{"_id":"@a11y-pulse/focus-not-obscured-audit","_rev":"5-f43242900f314c01cce609e2224d2a0a","name":"@a11y-pulse/focus-not-obscured-audit","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@a11y-pulse/focus-not-obscured-audit","version":"0.1.0","keywords":["accessibility","a11y","wcag","focus-not-obscured","puppeteer","audit"],"author":{"name":"A11y Pulse Limited"},"license":"SEE LICENSE IN LICENSE.md","_id":"@a11y-pulse/focus-not-obscured-audit@0.1.0","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/focus-not-obscured-audit#readme","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"82d6d795120ccf3e2f213585e494181368d98025","tarball":"https://registry.npmjs.org/@a11y-pulse/focus-not-obscured-audit/-/focus-not-obscured-audit-0.1.0.tgz","fileCount":9,"integrity":"sha512-o63ihjrk9Yb5I/c/mdiQp1/vV2H4/ScmctYnCvduXWjPQQAdzSfkuDq+h3WC+TKv38zpF3sp6nvomd3W8zfSkg==","signatures":[{"sig":"MEYCIQDr2vdeXkqIcNEKySA022nXmCskooTB4ELHR1jrrM2wBQIhAOWSlTgDheuoacV20XVNJXu1Z+qBSqFLQmYgboMrCoc+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":32488},"type":"module","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"16989f9d06b39c893b2ee2607651b8cdafc20d5b","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"},"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/focus-not-obscured-audit"},"_npmVersion":"11.11.0","description":"WCAG 2.4.11 Focus Not Obscured (Minimum) audit that tabs through a page and detects focused elements entirely hidden behind other content.","directories":{},"_nodeVersion":"24.13.0","dependencies":{"@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.7.0"},"_npmOperationalInternal":{"tmp":"tmp/focus-not-obscured-audit_0.1.0_1787202085947_0.757524673676073","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@a11y-pulse/focus-not-obscured-audit","version":"0.2.0","keywords":["accessibility","a11y","wcag","focus-not-obscured","puppeteer","audit"],"author":{"name":"A11y Pulse Limited"},"license":"SEE LICENSE IN LICENSE.md","_id":"@a11y-pulse/focus-not-obscured-audit@0.2.0","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/focus-not-obscured-audit#readme","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"e645e8f2a280585ca718453fea2fe9e6e7c059aa","tarball":"https://registry.npmjs.org/@a11y-pulse/focus-not-obscured-audit/-/focus-not-obscured-audit-0.2.0.tgz","fileCount":9,"integrity":"sha512-GJRhYba7Wmp0aC6jtqVF6I1sZ7nDIzcina4rpTqueB5GGxI/ShXv7pyIpdWw7MheEVBJuL4beZyIZBnVPGPupA==","signatures":[{"sig":"MEUCIFaqg6C0J0qNBloV+XoIr34/4LtHaq3TyzYfqTL/ZUezAiEAjDyJOfXOYEqjBbUXip6UJiIN1E2b7P/E66Xp/nXM/Q4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@a11y-pulse%2ffocus-not-obscured-audit@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":32488},"type":"module","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"f05e0097f3dc9b26151bb00db20bf4ba98707bda","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:34ca75df-9e2f-4939-9261-e4b4480c636b"}},"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/focus-not-obscured-audit"},"_npmVersion":"12.0.2","description":"WCAG 2.4.11 Focus Not Obscured (Minimum) audit that tabs through a page and detects focused elements entirely hidden behind other content.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.7.0"},"_npmOperationalInternal":{"tmp":"tmp/focus-not-obscured-audit_0.2.0_1787212646313_0.8870515389468856","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@a11y-pulse/focus-not-obscured-audit","version":"0.3.0","keywords":["accessibility","a11y","wcag","focus-not-obscured","puppeteer","audit"],"author":{"name":"A11y Pulse Limited"},"license":"SEE LICENSE IN LICENSE.md","_id":"@a11y-pulse/focus-not-obscured-audit@0.3.0","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/focus-not-obscured-audit#readme","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"43222db59fe5e3f514e6cc69854bd56acdedb33b","tarball":"https://registry.npmjs.org/@a11y-pulse/focus-not-obscured-audit/-/focus-not-obscured-audit-0.3.0.tgz","fileCount":9,"integrity":"sha512-WiAqTpBd8BQ0BdEqm/6ysum3CkWR93MtFx8nmjXJHPvrvZp6bFJWaVR7Cll0EeouVldE6yxzXmuwlzeslWnxxA==","signatures":[{"sig":"MEUCIQD9UXcKfyJ3DX/qBfTdLeQB9pajxU4HKPDQA5a2DsJZxAIgF6fdA94ACOTM+5KlhkI2zraryQyXHQa48EDijcTkddg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@a11y-pulse%2ffocus-not-obscured-audit@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":34208},"type":"module","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"cdf225620b08438311d55916e8c946f45bd61adc","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:34ca75df-9e2f-4939-9261-e4b4480c636b"}},"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/focus-not-obscured-audit"},"_npmVersion":"12.0.2","description":"WCAG 2.4.11 Focus Not Obscured (Minimum) audit that tabs through a page and detects focused elements entirely hidden behind other content.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.7.0"},"_npmOperationalInternal":{"tmp":"tmp/focus-not-obscured-audit_0.3.0_1787554135050_0.06554265290766259","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@a11y-pulse/focus-not-obscured-audit","version":"0.3.1","keywords":["accessibility","a11y","wcag","focus-not-obscured","puppeteer","audit"],"author":{"name":"A11y Pulse"},"license":"SEE LICENSE IN LICENSE.md","_id":"@a11y-pulse/focus-not-obscured-audit@0.3.1","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/focus-not-obscured-audit#readme","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"7de92d081e7fb6e805c4431443726c7cc5eba378","tarball":"https://registry.npmjs.org/@a11y-pulse/focus-not-obscured-audit/-/focus-not-obscured-audit-0.3.1.tgz","fileCount":9,"integrity":"sha512-EI+/a/ipk5MxROaghEwzWKX0AYhVLfM6Y19dti30Afbmu7bLnUU57dysSue3fmVg9jlNULYPKRHoCdMAmk44pA==","signatures":[{"sig":"MEYCIQDCvCAV4r03oPsZ3ILYo4NLDvv3CuojTpWiviTKKXN0ZwIhALH5laLmbNkVoTQGjXphqidp008XkWShP7EMBlpjesV9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@a11y-pulse%2ffocus-not-obscured-audit@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":34169},"type":"module","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","source":"./src/index.ts"}},"gitHead":"8431177b6098e09064306dc08bddfb59796b444d","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:34ca75df-9e2f-4939-9261-e4b4480c636b"}},"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/focus-not-obscured-audit"},"_npmVersion":"12.0.2","description":"WCAG 2.4.11 Focus Not Obscured (Minimum) audit that tabs through a page and detects focused elements entirely hidden behind other content.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.9.0"},"_npmOperationalInternal":{"tmp":"tmp/focus-not-obscured-audit_0.3.1_1788834835797_0.983725154398245","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"_id":"@a11y-pulse/focus-not-obscured-audit@0.4.0","bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"dist":{"shasum":"30d5cc3eb0bd97caded87ea5bfdd6249cf4f089f","tarball":"https://registry.npmjs.org/@a11y-pulse/focus-not-obscured-audit/-/focus-not-obscured-audit-0.4.0.tgz","fileCount":9,"integrity":"sha512-zIahhdE8uttGysg1P6bPsTv1ZRRjsBR4d0FLZL9ixcOk+0qEOr8G0PZVxfHFgwCbGAyvUyJZYWR4CTnWtm7sFg==","signatures":[{"sig":"MEQCIAxx2YMkDmqbms4ZWDe5Q/xcw0DQn4bySBMiMVhXMl+gAiBIi7XTpV1OtCgUWe2GQgI/uVGim0vM3WgWNXtAcoKANg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFXBvJ/+qjAQ1vpnOhGrVxnb9lDOu4KYeMEjARcKLb5WAiEAqJxX51ejcv6UsY54IxJfsl98/Wv/C2mo1M0KWQhuMPE="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@a11y-pulse%2ffocus-not-obscured-audit@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":35054},"name":"@a11y-pulse/focus-not-obscured-audit","type":"module","author":{"name":"A11y Pulse"},"engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","source":"./src/index.ts"}},"gitHead":"facc34c09fbb3982d29a1dc04944b5d035af7780","license":"SEE LICENSE IN LICENSE.md","scripts":{"lint":"biome check","test":"npm run typecheck && npm run test:unit","build":"tsup && tsc -p tsconfig.build.json","lint:fix":"biome check --fix","test:unit":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","test:integration":"vitest run --config vitest.integration.config.ts"},"version":"0.4.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:34ca75df-9e2f-4939-9261-e4b4480c636b"}},"homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/focus-not-obscured-audit#readme","keywords":["accessibility","a11y","wcag","focus-not-obscured","puppeteer","audit"],"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/focus-not-obscured-audit"},"_npmVersion":"12.0.2","description":"WCAG 2.4.11 Focus Not Obscured (Minimum) audit that tabs through a page and detects focused elements entirely hidden behind other content.","directories":{},"maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"_nodeVersion":"22.23.2","dependencies":{"@a11y-pulse/browser-adaptor":"*","@a11y-pulse/tab-orchestrator":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"puppeteer":"^25.10.0","playwright-core":"^1.63.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/focus-not-obscured-audit_0.4.0_1789286328060_0.5504939729956277"}}},"time":{"created":"2026-08-20T05:01:25.375Z","modified":"2026-09-13T07:58:49.495Z","0.1.0":"2026-08-20T05:01:26.100Z","0.2.0":"2026-08-20T07:57:26.453Z","0.3.0":"2026-08-24T06:48:55.188Z","0.3.1":"2026-09-08T02:33:55.928Z","0.4.0":"2026-09-13T07:58:48.154Z"},"bugs":{"url":"https://github.com/A11y-Pulse/audits/issues"},"author":{"name":"A11y Pulse"},"license":"SEE LICENSE IN LICENSE.md","homepage":"https://github.com/A11y-Pulse/audits/tree/main/packages/focus-not-obscured-audit#readme","keywords":["accessibility","a11y","wcag","focus-not-obscured","puppeteer","audit"],"repository":{"url":"git+https://github.com/A11y-Pulse/audits.git","type":"git","directory":"packages/focus-not-obscured-audit"},"description":"WCAG 2.4.11 Focus Not Obscured (Minimum) audit that tabs through a page and detects focused elements entirely hidden behind other content.","maintainers":[{"name":"wildlyinaccurate","email":"joseph@wildlyinaccurate.com"}],"readme":"# @a11y-pulse/focus-not-obscured-audit\n\n[![npm version](https://img.shields.io/npm/v/@a11y-pulse/focus-not-obscured-audit)](https://www.npmjs.com/package/@a11y-pulse/focus-not-obscured-audit)\n[![CI](https://github.com/A11y-Pulse/audits/actions/workflows/ci.yml/badge.svg)](https://github.com/A11y-Pulse/audits/actions/workflows/ci.yml)\n[![License: PolyForm Shield 1.0.0](https://img.shields.io/badge/license-PolyForm%20Shield%201.0.0-blue)](./LICENSE.md)\n\nAn accessibility audit that aims to verify compliance with [**WCAG 2.2: 2.4.11 Focus Not Obscured (Minimum)**](https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum). It tabs through a page's focusable elements and detects whether each one is entirely hidden behind other content (a sticky header/footer, a cookie banner, a modal overlay, etc.) while focused. It is built to be framework-agnostic and can be used in any environment that allows you to programmatically focus elements and hit-test the page, such as Puppeteer, Playwright, or Selenium.\n\nThis audit was developed by [A11y Pulse](https://www.a11ypulse.com/) for its accessibility monitoring service. It is released as source-available under the [PolyForm Shield License 1.0.0](#license).\n\n## Install\n\n```bash\nnpm install @a11y-pulse/focus-not-obscured-audit @a11y-pulse/tab-orchestrator @a11y-pulse/browser-adaptor puppeteer\n```\n\n`@a11y-pulse/tab-orchestrator` drives the page (tabbing, markers, obscuring measurement), and [`@a11y-pulse/browser-adaptor`](../browser-adaptor) ships the bundled [Puppeteer and Playwright adaptors](#adaptors); `puppeteer` and `playwright-core` are optional peers of that package, each only required if you use the matching adaptor. Other frameworks can supply their own adaptor without installing either.\n\n## Quickstart\n\n```js\nimport { runFocusNotObscuredAudit } from \"@a11y-pulse/focus-not-obscured-audit\";\nimport { PuppeteerAdaptor } from \"@a11y-pulse/browser-adaptor/puppeteer\";\nimport puppeteer from \"puppeteer\";\n\nconst browser = await puppeteer.launch();\nconst page = await browser.newPage();\nawait page.goto(\"https://who.likesdogs.nz/\");\n\nconst result = await runFocusNotObscuredAudit(new PuppeteerAdaptor(page), {\n  elementLimit: 20,\n});\n\nconsole.log(result.summary);\n// { checked: 2, passed: 1, failed: 1, reachedLimit: false, reachedFailedElementLimit: false, timedOut: false }\n\nconsole.log(result.elements);\n// [\n//   {\n//     selector: 'html>body>a#under-footer',\n//     html: '<a id=\"under-footer\" href=\"#under\">',\n//     tabIndex: 1,\n//     bucket: 'violation',\n//     measurement: {\n//       coveredFraction: 1,\n//       fullyObscured: true,\n//       offscreen: false,\n//       opacity: 'opaque',\n//       obscuredBy: { selector: '#sticky-footer', html: '<div id=\"sticky-footer\">' }\n//     }\n//   },\n// ]\n\nawait browser.close();\n```\n\nSee [`@a11y-pulse/focus-appearance-audit`'s `examples/puppeteer`](../focus-appearance-audit/examples/puppeteer) for a runnable example of the same shared-adaptor pattern.\n\n## Shared tab session\n\n`runFocusNotObscuredAudit` is a convenience wrapper: it builds a private `@a11y-pulse/tab-orchestrator` session, attaches one consumer, runs it, and hands back that consumer's result. If you're running more than one tab-driven audit against the same page (for example alongside [`@a11y-pulse/focus-appearance-audit`](../focus-appearance-audit)), drive a single shared orchestrator instead so the page is only tabbed through once. Use `createFocusNotObscuredAudit` to get a `TabConsumer` you can `attach()` yourself:\n\n```ts\nimport { createTabOrchestrator } from \"@a11y-pulse/tab-orchestrator\";\nimport { createFocusNotObscuredAudit } from \"@a11y-pulse/focus-not-obscured-audit\";\nimport { PuppeteerAdaptor } from \"@a11y-pulse/browser-adaptor/puppeteer\";\n\nconst orchestrator = createTabOrchestrator(new PuppeteerAdaptor(page));\n\nconst notObscured = createFocusNotObscuredAudit({ elementLimit: 50 });\norchestrator.attach(notObscured);\n\nawait orchestrator.run();\n\nconsole.log(notObscured.result);\n```\n\n`notObscured.result` is only complete once `notObscured` has disconnected (by hitting one of its own limits) or the session has ended. Reading it before then is undefined. See [`@a11y-pulse/tab-orchestrator`](../tab-orchestrator) for the full session lifecycle and capability model. This audit declares only the `\"obscuring\"` capability: it does not need `unfocusedPair` or `baselineStyles`, so it can run alongside `focus-appearance-audit` on the same orchestrator without either one paying for the other's measurements.\n\n## Browser support\n\n| Adaptor | Browser | Supported |\n| --- | --- | --- |\n| Puppeteer | Chrome | Yes |\n| Playwright | Chromium | Yes |\n| Playwright | WebKit | **No.** WebKit does not move focus to links when Tab is pressed, so the audit only reaches a subset of the page. |\n| Playwright | Firefox | **Partial.** An element behind a sticky footer is not reported as obscured, where Chromium reports a violation. |\n\nVerified by this repo's integration suites, which run every audit against each of these engines. Unsupported and partial cases are skipped there with the reason printed alongside them.\n\n## Options\n\nThe following options can be passed to `runFocusNotObscuredAudit` as `FocusNotObscuredOptions`:\n\n| Option                  | Type     | Default         | Description                                                                                                                                         |\n| ------------------------ | -------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `elementLimit`            | `number` | `1024`           | Max focusable elements to tab through.                                                                                                              |\n| `screenshotSettleDelay`   | `number` | `33`             | How long to wait (in ms) after each Tab for the page to settle before measuring.                                                                    |\n| `failedElementLimit`      | `number` | `0` (never)      | Finish the audit early once this many elements have failed, leaving the rest unchecked. Useful as a fail-fast signal when you only need to know a page has focus problems, not their full extent. |\n| `timeout`                 | `number` | `0` (no timeout) | Limit how long (in ms) the audit runs before returning the results it has gathered so far.                                                          |\n\n## Result shape\n\n`runFocusNotObscuredAudit` resolves to a `FocusNotObscuredResult`:\n\n```ts\ntype FocusNotObscuredResult = {\n  /** Every focusable element that was checked, in tab order. */\n  elements: Array<{\n    selector: string;\n    html: string;\n    tabIndex: number;\n    /** The AA bucket for this element (see below). */\n    bucket: \"violation\" | \"incomplete\" | \"pass\";\n    /** The raw obscuring measurement behind `bucket`. */\n    measurement: {\n      coveredFraction: number;\n      fullyObscured: boolean;\n      offscreen: boolean;\n      opacity: \"opaque\" | \"semi-transparent\" | \"unknown\";\n      obscuredBy: { selector: string; html: string } | null;\n    };\n  }>;\n  summary: {\n    checked: number;\n    passed: number;\n    /** Count of elements whose `bucket` is `\"violation\"`. */\n    failed: number;\n    /** True if `elementLimit` was hit before tabbing finished. */\n    reachedLimit: boolean;\n    /** True if the audit stopped early after hitting `failedElementLimit`. */\n    reachedFailedElementLimit: boolean;\n    /** True if the audit returned early because `timeout` elapsed. */\n    timedOut: boolean;\n  };\n};\n```\n\n### Buckets\n\n[2.4.11 Focus Not Obscured (Minimum)](https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum) is a Level AA criterion: when an element receives keyboard focus, it must not be **entirely** hidden by author-created content (partial coverage is allowed at this level; full protection from any coverage is [2.4.12 Focus Not Obscured (Enhanced)](https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-enhanced), a Level AAA criterion this audit does not check). Each element is placed in one of three buckets:\n\n- **`violation`**: the element is entirely hidden behind opaque content while focused. Fails 2.4.11.\n- **`incomplete`**: the audit could not confirm compliance either way: the element was scrolled fully offscreen when focused, or it's entirely covered by something of unknown opacity (rare; usually SVG/canvas-painted overlays the audit can't classify).\n- **`pass`**: the element is not obscured at all, only partially obscured, or entirely covered by a semi-transparent overlay (still visible through the cover, so not \"entirely hidden\").\n\n`summary.failed` counts only `\"violation\"` elements; `\"incomplete\"` elements count toward `summary.passed` since they are not confirmed failures.\n\n## Adaptors\n\nThe audit itself is framework-agnostic: it drives a page through an **adaptor**, a small interface of primitives (evaluate JS in the page, press Tab, hit-test the focused element, etc.) that the audit calls without knowing which browser automation library is behind it.\n\nThe `BrowserAdaptor` interface lives in [`@a11y-pulse/browser-adaptor`](../browser-adaptor), which also ships the bundled `PuppeteerAdaptor` and `PlaywrightAdaptor`. This package re-exports the type so `runFocusNotObscuredAudit`'s argument type is available without a separate import. Other environments (Selenium, WebDriver) can be supported by implementing the same interface.\n\nUse [`@a11y-pulse/browser-adaptor`'s `src/adaptors/puppeteer.ts`](../browser-adaptor/src/adaptors/puppeteer.ts) as a reference implementation.\n\n## Limitations\n\n- **Tab order only.** The audit tabs through elements in native tab order. It does not yet exercise arrow-key composite widgets (menus, comboboxes, toolbars, etc.) where focus moves via `aria-activedescendant` or roving `tabindex` instead of native Tab.\n- **Minimum, not Enhanced.** This audit checks 2.4.11 only. It does not check 2.4.12 (AAA), which requires that *no part* of the focused element be obscured, including by the user's own assistive technology.\n- **Opaque hit-testing.** Coverage is measured by sampling the focused element's box for unrelated content on top of it; unusual stacking/clip-path/mask combinations could in rare cases confuse the opacity classification, which is why those cases are routed to `\"incomplete\"` rather than guessed.\n\n## Releasing\n\nReleases are managed in the [A11y-Pulse/audits](https://github.com/A11y-Pulse/audits) monorepo with [Changesets](https://github.com/changesets/changesets). Publishing uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC). There is no long-lived `NPM_TOKEN`.\n\n### Ship a change\n\n1. Open a PR against `main` that includes a changeset (`npx changeset`) naming `@a11y-pulse/focus-not-obscured-audit`.\n2. After merge, the Release workflow opens a Version PR. Merging that PR publishes this package to npm and tags `@a11y-pulse/focus-not-obscured-audit@<version>`.\n\nTrusted Publisher on npm must stay configured for:\n\n| Field | Value |\n| --- | --- |\n| Organization or user | `A11y-Pulse` |\n| Repository | `audits` |\n| Workflow filename | `release.yml` |\n\n### Consumers (e.g. the A11y Pulse runner)\n\nBumping the published version in downstream apps is a separate change. Update the dependency range / lockfile there after the npm release lands.\n\n## License\n\nReleased under the [PolyForm Shield License 1.0.0](./LICENSE.md), in plain language:\n\n- **Source-available.** The source is public and you can read, fork, and modify it.\n- **Permitted for non-competing use.** You can use this package freely in your own products and services, as long as they don't compete with A11y Pulse.\n- **Competing products are forbidden.** You may not use this software (or a modified version of it) to build a product or service that competes with A11y Pulse's accessibility monitoring offering.\n\nSee [LICENSE.md](./LICENSE.md) for the full, binding terms.\n","readmeFilename":"README.md"}