diff --git a/lib/exporter/html.ts b/lib/exporter/html.ts
index 05e4422..45ce4a0 100644
--- a/lib/exporter/html.ts
+++ b/lib/exporter/html.ts
@@ -282,6 +282,17 @@ function buildJs() {
(function(){
var ts = document.querySelector('input[name="_ts"]');
if(ts){ ts.value = String(Date.now()); }
+ // Ensure skip-link moves focus into main for a11y
+ var skip = document.querySelector('a.skip-link');
+ var main = document.getElementById('main');
+ if (skip && main) {
+ skip.addEventListener('click', function(){
+ if (!main.hasAttribute('tabindex')) {
+ main.setAttribute('tabindex', '-1');
+ }
+ main.focus();
+ });
+ }
})();
`
}
diff --git a/tests/e2e/export-focus-outline.e2e.ts b/tests/e2e/export-focus-outline.e2e.ts
new file mode 100644
index 0000000..7f611fa
--- /dev/null
+++ b/tests/e2e/export-focus-outline.e2e.ts
@@ -0,0 +1,42 @@
+import { test, expect } from '@playwright/test'
+
+type WindowWithExport = Window & {
+ __exportPreview?: () => { html: string; css: string; js: string }
+}
+
+const builderUrl = '/en/builder'
+
+// Verify that focus outline tokens from CSS are applied in a real browser context
+// by focusing the submit button in the exported page and inspecting computed styles.
+test('Exported page shows 3px solid outline color on focused button', async ({ page, context }) => {
+ await page.goto(builderUrl)
+ // Minimal content to export
+ await page.getByTestId('card-hero').click()
+
+ await page.waitForFunction(() => Boolean((window as unknown as WindowWithExport).__exportPreview), { timeout: 5000 })
+ const exported = await page.evaluate(() => (window as unknown as WindowWithExport).__exportPreview?.() ?? null)
+ expect(exported).not.toBeNull()
+
+ const preview = await context.newPage()
+ await preview.setContent(exported!.html)
+ await preview.addStyleTag({ content: exported!.css })
+
+ const submit = preview.locator('button[type="submit"]')
+ await expect(submit).toHaveCount(1)
+ await submit.focus()
+
+ // Read computed style in the page context
+ const style = await preview.evaluate(() => {
+ const el = document.querySelector('button[type="submit"]') as HTMLElement | null
+ if (!el) return null
+ const cs = window.getComputedStyle(el)
+ return { outlineWidth: cs.outlineWidth, outlineStyle: cs.outlineStyle, outlineColor: cs.outlineColor }
+ })
+ expect(style).not.toBeNull()
+ // Expect 3px solid and brand color (#2563eb => rgb(37, 99, 235))
+ expect(style!.outlineWidth).toBe('3px')
+ expect(style!.outlineStyle).toBe('solid')
+ expect(style!.outlineColor.replace(/\s+/g, '')).toMatch(/rgb\(37,99,235\)|#2563eb/i)
+
+ await preview.close()
+})
diff --git a/tests/e2e/export-tab-order.e2e.ts b/tests/e2e/export-tab-order.e2e.ts
new file mode 100644
index 0000000..7a1e26b
--- /dev/null
+++ b/tests/e2e/export-tab-order.e2e.ts
@@ -0,0 +1,49 @@
+import { test, expect } from '@playwright/test'
+
+type WindowWithExport = Window & {
+ __exportPreview?: () => { html: string; css: string; js: string }
+}
+
+const builderUrl = '/en/builder'
+
+// Verify tab order after activating the skip-link: focus should move into
+// Then the first Tab should reach a focusable descendant (e.g., form submit button)
+test('Skip-link Enter moves focus into main; next Tab reaches first focusable', async ({ page, context }) => {
+ await page.goto(builderUrl)
+ await page.getByTestId('card-hero').click()
+
+ await page.waitForFunction(() => Boolean((window as unknown as WindowWithExport).__exportPreview), { timeout: 5000 })
+ const exported = await page.evaluate(() => (window as unknown as WindowWithExport).__exportPreview?.() ?? null)
+ expect(exported).not.toBeNull()
+
+ const preview = await context.newPage()
+ await preview.setContent(exported!.html)
+ await preview.addStyleTag({ content: exported!.css })
+
+ const skip = preview.locator('a.skip-link')
+ await expect(skip).toHaveCount(1)
+ await skip.focus()
+ await expect(skip).toBeFocused()
+
+ // Activate skip-link
+ await preview.keyboard.press('Enter')
+
+ // Focus should be inside #main or on #main
+ const focusedInMain = await preview.evaluate(() => {
+ const main = document.getElementById('main')!
+ const active = document.activeElement as HTMLElement | null
+ if (!active) return false
+ return active === main || main.contains(active)
+ })
+ expect(focusedInMain).toBeTruthy()
+
+ // Press Tab and expect a focusable inside main (submit button) to receive focus
+ await preview.keyboard.press('Tab')
+ const isSubmitFocused = await preview.evaluate(() => {
+ const active = document.activeElement as HTMLElement | null
+ return !!active && active.tagName.toLowerCase() === 'button' && (active as HTMLButtonElement).type === 'submit'
+ })
+ expect(isSubmitFocused).toBeTruthy()
+
+ await preview.close()
+})