Accessibility statement
Accessibility at LatchKey
Our dated report on what we have evaluated, the barriers we know about, and the work still ahead.
- Conformance target
- WCAG 2.2 Level AA
- Last evaluated
- 16 September 2026
- Next evaluation due
- 15 December 2026
- Report owner
- accessibility-lead
Version 1 · 9 surfaces in scope · 4 open or in-progress issues · 7 evaluation gaps · 90-day review cadence
Our commitment
We build accessibility tooling, so we hold our own surfaces to the standard we sell against: WCAG 2.2 Level AA. We check them with the same engine our scanner runs before a release goes out, and a state our harness cannot reach is reported as skipped rather than counted as passing. We publish what we have not fixed, below, and what we have not evaluated at all. And we answer a report about a barrier on this site from anyone, whether or not you are a customer.
One thing to be clear about before the detail: this page does not claim that thelatchkey.in is fully accessible, and it is not a compliance certification. Automated checks cover roughly one third of WCAG success criteria, which bounds what any gate of this kind can tell you, ours included. What automated testing cannot find applies to this site exactly as it applies to yours. Every claim below uses the same five values a procurement reviewer expects, and no surface claims Supports, because a claim of Supports needs a person driving assistive technology behind it and we have not done that yet.
What we evaluated, and how
Review the claim, evaluation date, and known issues for each surface. Expand an evaluation method to see what was tested, at which widths, and what was not tested.
Scroll within the table to explore the report. Focus it and use the arrow keys to navigate.
| Surface & evaluation | Conformance claim | Evaluation method | Known issues |
|---|---|---|---|
| thelatchkey.in marketing site, the free scanner and its result screensWCAG 2.2 Level AAEvaluated 18 September 2026 | Partially Supports | Read method for thelatchkey.in marketing site, the free scanner and its result screensaxe-core 4.10 driven by scripts/audit-self.mjs against the built site at 1440, 1200, 375 and 320 CSS pixels, including interactive states (an expanded issue row, a switched tab, an open share popover, an open download dialog). Result on 8 September 2026: 72 page-states checked, no violations, 14 designed states the run could not reach. RE-RUN on 18 September 2026, because /terms, /privacy and /cookies were added to this surface that day and a claim must not cover a page the run never saw: 128 page-states checked over the marketing routes, the free scanner and the 404, no violations, 18 designed states the run could not reach and named in its output. Each policy page was audited at all four widths, which matters because they are long documents with a sticky contents list and a wide table inside a focusable scroll container. No screen reader pass and no manual keyboard-only traversal has been done, so the claim rests on automated evidence plus source review only (gaps ACC-GAP-01 and ACC-GAP-02). | |
| thelatchkey.in portal: websites, account settings, team and billingWCAG 2.2 Level AAEvaluated 16 September 2026 | Partially Supports | Read method for thelatchkey.in portal: websites, account settings, team and billingFIRST EVALUATION, on 16 September 2026. This surface read Not Evaluated from the day the registry was written, and the reason recorded was that the states need a signed-in session the harness could not make. That was not true: scripts/audit-self.mjs has taken --portal-storage and --portal-projects the whole time, and seed-crawl-fixtures.ts has signed session cookies since the crawl screens were audited. What was missing was a script to produce them, which is now pnpm seed:portal-fixtures. axe-core 4.10 at 1440, 1200, 375 and 320 CSS pixels over a signed-in session: the seven account panels, fourteen screens per website across three websites in three licence states (a trial, an expired licence, and no licence at all, because no-license.tsx, license-banner.tsx and panel-gate.tsx all branch on exactly that), the plan-activation screen against a live pending checkout binding, the invitation acceptance screen against a live invite from a second account, and the mobile navigation drawer opened. 206 portal page-states checked, no violations; in the same run as the marketing routes and the status page, 326 page-states and no violations in total. The claim is Partially Supports rather than Supports because no screen reader pass and no manual keyboard-only traversal has been done, which is ACC-GAP-01 and ACC-GAP-02 and applies to every surface on this page. Two states remain unaudited and are named rather than assumed: the drawer at desktop widths, where it is not rendered, and the invitation screens for a withdrawn, expired or wrong-address link, which need their own rows. ADDED on 1 October 2026 with the Issues screens: fourteen states from the crawl fixture seed (the issue list in five shapes, the by-rule and by-page views, and the issue drawer open, dismissed, with each of its two dialogs open, refusing a stale change, and read-only for a viewer) at the same four widths, 56 page-states with no violations, in a run of 144 crawl page-states with none. ADDED the same day with batch actions: nine more states (a selection with the batch toolbar, a batch under review, all of a view selected, the typed-count confirmation, the verify-fix quote, and a batch’s page finished, partly applied, undone and stopped part way), 36 page-states with no violations, in a run of 180 crawl page-states with none. ADDED the same day with site settings: seventeen more states (the settings sections, crawl exclusions kept, refused, empty and read-only, the rule screen in seven shapes including a refused note and a set flagged for review, a crawl report, history and page table scored with a website’s own rule set, and the crawl form with the website’s exclusions), 68 page-states with no violations, in a run of 248 crawl page-states with none. ADDED the same day with manual import and files: eighteen more states (the import page three ways, its preview in six shapes, an import imported and one left, the audit history with a manual audit and a broken trend, an imported finding in the list and the drawer, and the files page four ways), 72 page-states with no violations, in a run of 320 crawl page-states with none. A first run found the trend chart’s scroll container unreachable by keyboard at 375 and 320 CSS pixels (scrollable-region-focusable); it was made focusable and the whole run repeated. ADDED the same day with API tokens: six more states (the token list, a token shown once, the revoke dialog, an account with none, the list read by a member, and the account settings page), 24 page-states with no violations, in a run of 344 crawl page-states with none. ADDED the same day with journeys: sixteen more states (the journey list with live-site, brittle, paused and password journeys, with none recorded, without a Crawler plan and read by a viewer; the builder new, reopened and refusing a save with every problem listed; a journey with its runs, brittle and paused with a run waiting, asking for a password to run, with its delete dialog open, and read by a viewer; a run that reached its page, one stopped at a step with its screenshots and page copy, and one waiting for a worker; and the issue list showing what journeys found), 64 page-states with no violations, in a run of 408 crawl page-states with none. A first run of the builder’s refusal state failed in the harness, not the page: the hook’s label matched three fields, and it was made exact. ADDED the same day with auto-resolve: seven more states (the auto-resolve screen holding three issues with one ending this week, its notice that cannot be dismissed and its honest line; the controls for one hold, open; the screen read by a viewer; the screen on a website with no widget licence; an issue held by a runtime fix; the issue list’s fixed-at-runtime tab; and the project overview counting what is fixed only at runtime), 28 page-states with no violations, in a run of 436 crawl page-states with none. ADDED the same day with tracker integrations: eight more states (the integrations list, a Jira connection with its webhook, mapping, paused link and deliveries, a Linear connection waiting for its mapping and secret, the GitHub connect form, a connection read by a viewer, and three issue pages: a ticket closed while the barrier still fails, a ticket deleted, and an issue ready to send), 32 page-states with no violations, in a run of 468 crawl page-states with none. ADDED the same day with the GitHub App: six more states (an installation with its repositories, its recent reviews and a pause rate high enough to say the defaults look wrong; just connected; an installation connected to another account; the page read by a viewer; an account with none connected and no Crawler plan; and the integrations list with the App connected), 24 page-states with no violations, in a run of 492 crawl page-states with none. A first run found the starting latchkey.yml’s scroll container unreachable by keyboard at 375 and 320 CSS pixels (scrollable-region-focusable); it was made focusable and the whole run repeated. | |
| thelatchkey.in/help help centreWCAG 2.2 Level AAEvaluated 8 September 2026 | Partially Supports | Read method for thelatchkey.in/help help centreServed from the same Next.js application and the same marketing layout as the site above, and audited in the same run: axe-core 4.10 at 1440, 1200, 375 and 320 CSS pixels with no violations on 8 September 2026. It inherits the shared footer, so it inherits issue ACC-003. No screen reader pass and no manual keyboard-only traversal (ACC-GAP-01, ACC-GAP-02). | |
| thelatchkey.in/status status pageWCAG 2.2 Level AAEvaluated 16 September 2026 | Partially Supports | Read method for thelatchkey.in/status status pageRe-audited on 16 September 2026, after the component rows were rewritten to carry real health observations, in an axe-core 4.10 run at 1440, 1200, 375 and 320 CSS pixels with no violations. TWO states were audited rather than one: the page as the database happened to hold it, and a seeded state that renders every row treatment at once -- a fresh serving row, a degraded row, a not-serving row, a row stale enough to read as unmonitored, a probed component that has never reported, and an unprobeable one with its reason. That second state exists because each row reads component_health and none of them can be selected by a URL, so without `pnpm seed:health-fixtures` the audit reaches one row treatment out of six and on a healthy machine it is the least interesting one. The audit asserts the four chips are present and fails the state if the seed did not run, so it cannot pass on a page showing one green row. An incident state and a maintenance state do not exist yet. It inherits the shared footer and issue ACC-003. No screen reader pass and no manual keyboard-only traversal (ACC-GAP-01, ACC-GAP-02). | |
| The widget's own visitor interface, as shipped, in a Shadow DOM host on a fixture siteWCAG 2.2 Level AANo evaluation yet | Not Evaluated | Read method for The widget's own visitor interface, as shipped, in a Shadow DOM host on a fixture siteThe widget browser corpus (packages/latchkey-widget/test/browser, 39 cases across 6 files) loads the shipped loader and interface bundles in a real browser on a fixture page and asserts mounting into a Shadow DOM host, initialisation, lifecycle, behaviour under a strict Content Security Policy, isolation from host CSS, and locale resolution. That is behavioural evidence, not a conformance evaluation: the corpus runs no axe-core pass over the interface and no screen reader has ever been pointed at it. Two defects listed below were found by reading the source rather than by evaluating the surface, which is why they appear here under a claim of Not Evaluated. First evaluation is scheduled for Phase 2 with the axe pass over the mounted interface. | |
| The embedded Shopify app at /embedded/shopify and the Wix dashboard page at /embedded/wix, and the connect pages they open at /connect/shopify/{code} and /connect/wix/{code}WCAG 2.2 Level AANo evaluation yet | Not Evaluated | Read method for The embedded Shopify app at /embedded/shopify and the Wix dashboard page at /embedded/wix, and the connect pages they open at /connect/shopify/{code} and /connect/wix/{code}scripts/audit-self.mjs drives axe-core 4.13.0 over the embedded app UNFRAMED, at 1440, 1200, 375 and 320 CSS pixels in both the light and the dark colour scheme: App Bridge is replaced by a stub that hands out a fixture token, and the session API is answered with views generated by the real view builder (latchkey-core shopify/view-fixtures.ts), so every state audited carries the words a merchant would read. The top-level connect pages are audited from rows pnpm seed:portal-fixtures writes, one per state, and each state is audited only once its own heading has rendered, so a stale fixture fails the run instead of being counted under the name of another state. Run on 24 September 2026 against a development server holding the fixture Shopify credentials: 32 states (twenty of the embedded app, twelve of the connect page) at four widths in two schemes, 256 page-states, no violations. The Wix dashboard page is audited the same way, with no Wix script to stub: the page is loaded with a fixture instance in its address, which only the stubbed session route ever sees, and the session is answered with views generated by latchkey-core wix/view-fixtures.ts. Run on 2 October 2026 against a development server holding the fixture Wix credentials: 26 states (twenty-four of the dashboard page, the page opened with no instance, and the Wix connect page’s unknown link) at four widths in two schemes, 208 page-states, no violations; and against one with no Wix credentials, as production is today, the not-configured page and the same connect link, 16 page-states, no violations. The Wix connect page’s other states are not seeded for Wix: it is the same component as Shopify’s, audited above in a store’s words. The widget browser suite renders every fixture view of both apps through the shipped client bundles and checks focus order and the new-tab rules. That is behavioural evidence, not a conformance evaluation: neither app has been loaded inside its platform’s admin, whose frame, navigation and focus handling surround it there, and no keyboard-only or screen reader pass has been run against either in or out of the frame (ACC-GAP-07). | None recorded |
| Published accessibility statement pages at /statement/{publicId}WCAG 2.2 Level AANo evaluation yet | Not Evaluated | Read method for Published accessibility statement pages at /statement/{publicId}The route is not built yet. A request to /statement/{publicId} on the current build returns 404, so there is no surface to evaluate and no claim is being made. The statement generator and its public pages land in Phase 2, and the first evaluation is scheduled with them. | None recorded |
| Public Evidence Pack share view at /share/evidence-pack/{token}WCAG 2.2 Level AANo evaluation yet | Not Evaluated | Read method for Public Evidence Pack share view at /share/evidence-pack/{token}The route is not built yet. Sharing lands in Phase 5 module 10, and the surface is registered here before it exists so that the first evaluation is a precondition of shipping it rather than a follow-up. When it does ship it is audited in both themes and gets a manual pass, not only axe: the passcode gate, the access-denied states and the read-only pack render are each their own page-state. | None recorded |
| PDF deliverables: the Remediation Report and the Evidence PackPDF/UA-1 with WCAG 2.2 Level AA Level AANo evaluation yet | Not Evaluated | Read method for PDF deliverables: the Remediation Report and the Evidence PackA tagged PDF is a different conformance question from a web page and needs its own tool. The only print surface that exists today is /scan/{scanId}/print, rendered by the (print) route group as HTML for the browser to print. It is not in the self-audit route map, no tagged-PDF or PDF/UA check has been run against any generated file, and this repository contains no such checker. Choosing one and running it is Phase 2 work owned by reports-eng. | None recorded |
What each conformance claim means
The five values are the reporting vocabulary a procurement reviewer already knows. Not Evaluated is one of them, and it is used here rather than leaving a blank or borrowing another surface’s claim.
- Supports
- Every part of the surface we evaluated meets the criterion.
- Partially Supports
- Some functionality on the surface does not meet the criterion.
- Does Not Support
- The majority of the functionality on the surface does not meet the criterion.
- Not Applicable
- The criterion is not relevant to this surface.
- Not Evaluated
- The surface has not been evaluated against the criterion. No claim is being made.
Out of scope, and why
Three surfaces a reader could reasonably expect to find above are not ours to claim for. A vendor that quietly includes a checkout it does not control in its own conformance claim is making a claim it cannot support, and a vendor that quietly drops the line saying so leaves a reader to assume it does control it.
- Dodo Payments' hosted checkout and its customer portal
- Payment is taken on a page Dodo Payments serves and controls. We do not write its markup, we cannot fix a barrier in it, and we would be making a claim we cannot support if we counted it as ours. We link their own statement instead, and a barrier reported to us on that page is forwarded with a copy to the reporter. Their own accessibility statement.
- Embedded platform admin frames (Shopify, Wix, Webflow and other marketplace hosts)
- Our app renders inside chrome the platform owns: their navigation, their modals, their focus management, their colour tokens. We claim the interface we render inside that frame and nothing around it, because the surrounding controls are not ours to evaluate or to fix.
- Customer websites the widget runs on
- A customer site is the thing our product works on, not a surface we can claim for. Its markup is the customer's, the widget adjusts a defined set of properties on it, and the coverage table on each result says what was and was not addressed. Reporting a customer site as conforming because our widget is installed on it is the overlay claim this company exists to argue against.
Known issues
Published barriers, who they affect, available workarounds, and planned fixes. Fixed issues remain visible with their resolution dates. Each issue has a permanent link.
- Open (4)
- In progress (0)
- Fixed (3)
Scroll within the table to explore the report. Focus it and use the arrow keys to navigate.
| Issue | Impact & criterion | Workaround | Owner & target | Status |
|---|---|---|---|---|
| ACC-001 The widget panel never declares the language of its own content. The panel sets its text direction from the active translator on every render, but no code anywhere in the widget sets a lang attribute, so a panel rendered in Japanese inside an English host page is announced with the English voice and English pronunciation rules. | 3.1.2 Language of Parts (AA) Screen reader users on any host page whose language differs from the interface language they chose, which is the normal case for a visitor who switches the panel to their own language on an English site. | None inside the widget. A site owner whose visitors mostly share one language can set the widget default language to match the host page lang, so the two at least agree. | widget-engTarget: Phase 2 | Open |
| ACC-002 Two of the 35 interface languages the widget offers are not translated. The Vietnamese and Simplified Chinese locale files ship English strings, so a visitor who selects either gets an English interface from a menu that offered them their own language. | 3.1.2 Language of Parts (AA) Mapped to 3.1.2 because the part is served in a language other than the one it is presented as. A reader could reasonably file this as a localisation defect rather than a WCAG failure. It is on the list either way. Vietnamese and Simplified Chinese speaking visitors, who are the people least able to use the fallback they are given. | The other 33 languages are translated, and English is served rather than blank strings, so the interface stays operable for anyone who reads some English. | widget-engTarget: Phase 2 | Open |
| ACC-003 Four links in the site footer point at pages that do not exist. Privacy, Cookies and local storage, Billing terms and Evidence Pack policy all return 404, so a reader following the footer to a policy page lands on an error. thelatchkey.in marketing site, thelatchkey.in/help help centre, thelatchkey.in/status status page | 2.4.4 Link Purpose (In Context) (A) Mapped to 2.4.4 on the reading that a link whose stated purpose cannot be fulfilled does not let a reader determine where it goes. The mapping is arguable. The defect is not. Everyone, and most sharply anyone who relies on the footer as the predictable route to policy pages rather than on search, including screen reader users navigating by landmark and link list. | No workaround available. A fix is needed. | legal-opsTarget: Phase 2 | Open |
| ACC-004 The share control on a scan result computes whether the result has expired once, while rendering. If a result expires while the page is open, the button keeps reporting itself as available: aria-disabled stays false and the tooltip that says the result has expired never appears. React's own purity rule flags the call as an error. | 4.1.2 Name, Role, Value (A) The state a control exposes to assistive technology disagrees with the state the control is actually in, which is a 4.1.2 failure on the value and state half of the criterion. Anyone who leaves a result page open past its retention window, and screen reader users in particular, because the disabled state is the only signal the control gives them that the link will not work. | Reloading the page recomputes the state. Nothing on the page tells you to. | growth-engTarget: Phase 2 | Open |
| ACC-005 Every primary button rendered dark ink on brand green at 2.6:1, on the home page of an accessibility product. The cause was a utility name collision: the type scale was implemented as text-body-lg, text-h1 and so on, and tailwind-merge cannot tell a custom text-* utility from a text colour, so it treated them as one group and silently dropped the colour. The scale is now type-*, which cannot collide by construction. | 1.4.3 Contrast (Minimum) (AA) Anyone with low vision or reading in bright light, on every primary action on the site. It was invisible in development because the scaffold forced dark mode and dark ink on a dark ground happened to pass. | No workaround available. A fix is needed. | design-systemTarget: Phase 0 | FixedFixed 7 September 2026 |
| ACC-006 Every control was 32 pixels tall, below both WCAG 2.5.8 and the design system's own 44 pixel floor, and navigation links sized themselves to their text. Fixed in the Button component rather than at each call site, because per-caller means the next caller gets it wrong. | 2.5.8 Target Size (Minimum) (AA) Anyone with a tremor, limited fine motor control, or a touch screen, on every button and navigation link in the product. | No workaround available. A fix is needed. | design-systemTarget: Phase 0 | FixedFixed 7 September 2026 |
| ACC-007 The 404 page had no lang attribute and no title, because notFound() called from inside a page makes Next render its own error document instead of the root layout. The missing-result cases now redirect to a path that deliberately has no route, so Next renders the real root boundary and the page gets its language and its title back. | 3.1.1 Language of Page (A) Recorded against 3.1.1 for the missing language. The same defect also failed 2.4.2 Page Titled; one row, two criteria, and this field holds one, which is a limitation of the row shape rather than of the finding. Screen reader users reaching a missing or expired result, who got the page read in whatever language their reader defaulted to, with no title to tell them where they were. | No workaround available. A fix is needed. | platform-engTarget: Phase 0 | FixedFixed 7 September 2026 |
Where a row is confirmed, the registry also records the command, file or commit that confirms it, so a row nobody can trace back to evidence fails our build rather than padding this list.
No row is withheld from this table. A row can be withheld only with a written reason, which appears here in place of the row, and a withheld row with no reason fails the build. That friction is the control: shortening this list leaves a trace in review.
Evaluation gaps
No screen reader has been run against any LatchKey surface. NVDA, JAWS, VoiceOver and TalkBack passes have not been done, and this report records that as an evaluation gap with an owner and a target phase rather than as a pass. Where a surface has had nothing run against it at all, its claim above reads Not Evaluated. Each gap below carries the sentence that matters most: what a reader must not conclude from the table above.
How this site is checked before it ships
Automated checks run across four viewport sizes. The coverage table records which pages and states are checked and which are skipped.
How the testing and validation work
The gate is a script in our repository: scripts/audit-self.mjs. It launches a headless Chromium, opens each page of this site that it knows about, injects axe-core 4.10 or later (the same engine every finding in our product comes from) and asks it for the five rule tags the script names: WCAG 2.0 and 2.1 at Level A and AA, plus the Level AA additions in 2.2. A violation makes the run exit non-zero, which is what stops the release: this is a gate rather than a report someone reads later.
Every page is checked at four widths: 1440, 1200, 375 and 320 CSS pixels. Neither end of that range is decoration. WCAG 1.4.10 states reflow at 320 pixels, and 320 is where a sticky bar, a wide table or a fixed-width code block starts covering something. 375 alone never catches it. At the other end, the header’s dropdown menu only renders from 1280 up, so a list that stopped at 1200 would audit the mobile sheet at every width and never once load the menu it replaces.
The phrase that does the work is “in every designed state”. A page can pass empty and fail with content, or pass collapsed and fail expanded, so states are checked separately. When the harness cannot reach one, it prints it in a “states not audited” block and the summary line says how many were missed. A gate that printed a green line for a screen it never loaded would be worse than no gate at all. The states it cannot reach today are recorded above as gap ACC-GAP-03, because the result screens are the most consequential states on this site and they are the unaudited ones.
A second script, scripts/lint-conformance.mjs, validates the report you are reading. It fails the build when the evaluation is more than 120 days old, when a surface has no claim, when a claim uses a word outside the five values, when a claim of Supports has no assistive technology pass behind it, when a known issue cites a criterion that is not in WCAG 2.2, and when a row is withheld from the table above without a written reason.
What the gate covers today
The result screens each need a scan already in that state, supplied as a fixture id on the command line. Run without fixtures (which is the usual local run) and those states are reported as skipped, never as passing.
Scroll within the table to explore the report. Focus it and use the arrow keys to navigate.
| Page or state | In the gate today | What that means |
|---|---|---|
| Marketing home | Audited | / covers the hero, the scan form, the dark band and the footer, at every width the run uses. |
| Free scan landing | Audited | /scan as a visitor first sees it. |
| Scan landing, prefilled | Audited | An address arriving in the query string, which renders a different form state. |
| Scan landing, inline validation error | Audited, with a worker running | Reached by submitting an invalid address. Client-side validation only, so it needs no fixture and burns no scan quota. It does need a reachable worker: when the health probe fails, /scan replaces the whole form with a maintenance banner and there is no address field to submit. A run made without one reports this state as skipped and names the worker as the reason; the gate run supplies either a worker or a loopback health stub. |
| Navigation, mega-menu open | Audited from 1280 up | The header dropdown is a designed state of its own: its links, its column headings and its promo panel are not in the page at all until a trigger is pressed. Below 1280 that component is not rendered, so the run reports this state as skipped rather than as passing. |
| Navigation, mobile sheet open | Audited below 1280 | The same navigation as a sheet over the page, with one group expanded so the accordion’s open state is audited too. From 1280 up the hamburger is not rendered and this one reports as skipped. |
| Limitations | Audited | /limitations, the page that carries the coverage argument. |
| The rest of the marketing pages | Audited | /widget, /crawler, /services, /pricing, /compliance, /accessibility, /about, /contact, /help, /status and /signup: every page the header and the footer name, at the same four widths. |
| Log in | Audited | /login, including its form labelling. |
| Not-found page | Audited | An unmatched scan path. The run also asserts the response really is a 404, because the shell Next renders for a page-level notFound() has no lang attribute and passed nothing. |
| Result screen: barriers found | Skipped without a fixture | Needs a completed scan with barriers. Seven states are checked from that one fixture, including a row expanded, each results tab, and the share and download dialogs. |
| Result screen: no failures with checks to review | Skipped without a fixture | Needs a completed scan with no violations and at least one check that needs a person. |
| Result screen: no automated failures | Skipped without a fixture | Needs a completed scan with no violations and nothing to review. |
| Result screen: scan failed | Skipped without a fixture | Needs a scan row in the failed state. |
| Result screen: result expired | Skipped without a fixture | Needs a scan past its expiry, or one whose screenshot has been purged. |
| Result screen: scan in progress | Skipped without a fixture | Needs a scan still queued or running. |
| Embedded Shopify app, every state | Audited unframed, in both colour schemes | /embedded/shopify with App Bridge replaced by a stub and the session answered by views from the real view builder: connecting, opened outside the admin, a session that failed, connect, a connection pending and one refused, the spent trial, the site key on its way and refused, and every storefront check result, in light and dark. It needs the server started with its Shopify credentials; without them the page serves its not-available state and the run reports the rest as skipped. It has never been driven inside the Shopify admin frame, and no keyboard or screen reader pass has been run (ACC-GAP-07). |
| Shopify connect page, unknown link | Audited | /connect/shopify with a code nobody issued, in both colour schemes. It needs no fixture and no session, because whoever holds a broken link may have no account at all. |
| Wix dashboard page, every state | Audited unframed, in both colour schemes | /embedded/wix with the session answered by views from the real view builder and a fixture instance in the page’s address: connecting, opened outside the dashboard, a session that failed, someone who is not the owner, the app removed, a site not yet published, one on a free Wix address, connect, a connection pending and one refused, the spent trial, the script on its way, being updated and refused, a licence billed directly, restored settings, and every site check result, in light and dark. It needs the server started with its Wix credentials; without them the page serves its not-available state, which is audited instead, and the run reports the rest as skipped. It has never been driven inside the Wix dashboard frame, and no keyboard or screen reader pass has been run (ACC-GAP-07). |
| Wix connect page, unknown link | Audited | /connect/wix with a code nobody issued, in both colour schemes, in a Wix site’s words. It needs no fixture and no session. |
| Wix connect page, every other state | Skipped without a fixture | The portal fixtures seed connect codes for Shopify stores only. The page is the same component for both platforms, so its signed-out, account-choice, connected and refused states are audited through Shopify’s, in a store’s words rather than a site’s. |
| Issues and the issue drawer | Audited from seeded rows | The issue list as a crawled site first shows it, its dismissed tab, a filter that matches nothing, a shared link that does not decode and a project never crawled; the same issues by rule and by page; and the issue drawer open, dismissed, with its dismiss and mark-as-fixed dialogs open, refusing a change because someone else updated the issue first, and as a viewer with every control disabled. The crawl fixture seed writes a settled crawl with real issues for them; without its manifest the run reports them as skipped. |
| Batch actions on issues | Audited from seeded rows | Two issues selected with the batch toolbar showing, a batch dismissal under review in its dialog, all six hundred issues that match a view selected, the dialog asking for the count to be typed above five hundred, the verify-fix quote in the issue drawer, and a batch’s own page finished, with a change left alone, undone, and stopped part way. Seeded by the crawl fixture seed; without its manifest the run reports them as skipped. |
| Site settings and scores over a rule set | Audited from seeded rows | The project’s settings with their section links; crawl exclusions kept and counted, with a regular expression refused, with none kept, and read by a viewer; rules and scoring with two rules excluded, its excluded and best-practice tabs, a note asked for under “Something else”, the standard set, a level A set flagged for review with its above-your-level tab, and read by a viewer; a crawl report, its history and its page table scored with the website’s own rule set; and the crawl form showing the website’s exclusions. Seeded by the crawl fixture seed; without its manifest the run reports them as skipped. |
| Manual import, files and the audit history | Audited from seeded rows | The import page with earlier imports, with none yet, and refusing an upload under its form; the import preview with rows that cannot be read, for a file imported before, in its detected encoding and read as another, for a file nothing can be read from, and read by a viewer; an import imported and one left; the audit history with a manual audit and a trend that breaks between runs of different sizes; the issue list and the drawer with an imported finding; and the files page grouped by kind, for one kind, for a crawl that found none, and for a website never crawled. Seeded by the crawl fixture seed; without its manifest the run reports them as skipped. |
| API tokens | Audited from seeded rows | The token list with active, IP-restricted and revoked tokens and their reasons; a new token shown once behind its saved-it gate; a revocation asked about in its dialog; an account that has made none; the list read by a member who cannot manage it; and the account settings page without its old API key row. Seeded by the crawl fixture seed; without its manifest the run reports them as skipped. |
| Journeys | Audited from seeded rows | The journey list with journeys that run on the live website, one brittle at a step, one paused, and one that asks for a password; the list with none recorded, without a Crawler plan, and read by a viewer; the builder for a new journey, reopened for an existing one, and refusing a save with every problem listed and linked to its step; a journey with its runs, one brittle and paused with a run waiting, one asking for a password to run, its delete dialog open, and read by a viewer; a run that reached its page, a run stopped at a step with its screenshots and the page it stopped on, and a run waiting for a worker; and the issue list filtered to what journeys found. Seeded by the crawl fixture seed; without its manifest the run reports them as skipped. |
| Auto-resolve | Audited from seeded rows | The auto-resolve screen holding three issues, one ending this week, with its notice that cannot be dismissed and its line saying more is held at runtime than fixed in the source; the extension and stop controls for one hold, open; the screen read by a viewer; the screen on a website with no widget licence; an issue held by a runtime fix, with the day it ends; the issue list on its fixed-at-runtime tab; and the project overview counting what is fixed only at runtime. Seeded by the crawl fixture seed; without its manifest the run reports them as skipped. |
| Tracker integrations | Audited from seeded rows | The integrations list with Jira and Linear connected and GitHub Issues not; the Jira connection with its webhook address and secret, its mapping, a paused link and its recent deliveries; the Linear connection waiting for its mapping and signing secret; the GitHub connect form; the Jira connection read by a viewer, without its secret; an issue whose ticket was closed while it still fails, saying so; an issue whose ticket was deleted; and an issue that can be sent to a tracker. Seeded by the crawl fixture seed with in-process fake trackers; without its manifest the run reports them as skipped. |
| GitHub App | Audited from seeded rows | The GitHub App page with an installation, its repositories, its recent reviews and a pause rate high enough to say the defaults look wrong; just connected; an installation connected to another account; the page read by a viewer, without the install and connect controls; an account with none connected and no Crawler plan; and the integrations list with the App connected. Seeded by the crawl fixture seed; without its manifest the run reports them as skipped. |
| Shopify connect page, every other state | Audited from seeded rows | An expired link, a spent one, a store that has removed the app, signed out, choosing an account, a member who cannot connect, a store held by another account, a store already held by the only account the person can act for, and the three results after a confirm. Each needs a connect code in that state, which the portal fixture seed writes, and each is audited only once its own heading has rendered; without the manifest from the seed the run reports them as skipped. |
Evidence, not a promise
What our own gate caught on our own pages
ACC-005 · Fixed 7 September 2026
1.4.3 Contrast (Minimum) (AA)
ACC-006 · Fixed 7 September 2026
2.5.8 Target Size (Minimum) (AA)
ACC-007 · Fixed 7 September 2026
3.1.1 Language of Page (A)
None of these were found by a reviewer being generous. Each one was written, pushed, and stopped by the run, which is the argument for having a gate at all, and the reason this section is longer than the one about our commitment. They stay on the known-issues table above with the date they were fixed.
Limits of the method itself
Separate from the issues above and from the gaps: these are limits of how we evaluate, not defects and not surfaces. Written the way we would want a vendor to write it for us. If one of these matters to you and is not moving, say so. A limitation that has been listed for a year without being worked on is a different problem from a limitation.
The same third applies to us
Automated checks cover roughly one third of WCAG success criteria, and this site is checked by the same automation we sell. So a run with nothing to report means the automated third found nothing on the states it reached. It says nothing about whether our reading order matches our visual order, whether our alt text is any good, or whether a form error makes sense read aloud out of context.
The run collects violations, not the maybes
The gate asks axe for violations only. Checks axe cannot decide on its own (the ones our own product reports as needing review) are not collected, so they are not counted here either way. That is a deliberate scope for a build gate and a real gap in this document.
The design system is light-only, on purpose
No dark palette has been designed. The contrast figures our palette is built on (brand indigo on white, danger red on its own tint) are properties of the light palette and do not transfer, and an invented dark palette would ship contrast failures on a product that sells accessibility. If you browse with a dark-mode preference, this site stays light.
A page not on the gate’s list is not audited by it
The gate opens the pages it has been given and discovers nothing on its own. Every page of this site we have actually published is on that list today, but a page added tomorrow is not covered until someone adds it there too, and neither is a state nobody wrote a step for. The printable version of a result is one we have not added. We would rather name the gap than describe the gate as if it found pages by itself.
We are in private beta, and some pages are still being written
A link in the navigation can still reach a page that is not published yet. That is a broken journey for anyone, and a worse one for someone using a screen reader who has to work out whether the mistake was theirs. Four such links are on the known-issues list above as ACC-003.
What this site relies on
Accessibility here depends on these technologies working in your browser and with your assistive technology. Where one of them is unavailable, the parts of the site that need it degrade rather than disappear: the marketing pages, the documentation and the published reports are readable with JavaScript off, and the portal is not.
- HTML, used semantically, with landmarks and headings as the primary structure
- CSS, including a light-only design system and reduced-motion support
- JavaScript, for the portal and for interactive components
- WAI-ARIA, where a native element cannot express the state
- SVG, for icons and diagrams, each one either labelled or hidden
- PDF, for reports and Evidence Packs, tagged when we generate them
The target is WCAG 2.2 Level AA. We do not claim conformance for a surface we have not evaluated, and the table above names every one we have.
How this was evaluated, and by whom
This is a self-evaluation. No third party has audited this website, and no part of this report was produced or reviewed by an external specialist. When that changes, this section will name who did it and when.
- Automated, on every build
- axe-core runs against every page of this site in every designed state and at four viewports. A violation fails the build, which is the gate described above rather than a report somebody reads afterwards.
- By hand, by us
- Keyboard-only walkthroughs of each flow, focus order and visible focus, forms and their error messages, zoom to 200 percent and reflow at 320px. Done by the people who built the thing being tested, which is a real limitation of it.
- Not done
- No screen reader has been run against any surface, so no claim on this page rests on assistive-technology testing. That is why a claim of Supports is not used where it would need one, and it is the single biggest gap in this report.
- Re-evaluated
- Every 90 days, and the build fails once the evaluation is more than 120 days old, so this document cannot quietly rot.
Report a barrier on this site
This document
- Version and dates
- Version 1, evaluated 16 September 2026, next evaluation due 15 December 2026 on a 90 day cadence. Version 1 is the first dated report rather than the fifth. When it is re-evaluated the version goes up and the dates move with it. There is no version archive at its own address yet, so an older version of this report is not linkable today.
- Owner
- accessibility-lead. Barrier reports go to accessibility@thelatchkey.in.
- Both names resolve
- /accessibility-statement is a permanent redirect (HTTP 308) to this page, because both names are used in our own plan and in the site footer, and a link from either should reach the report rather than a 404.
- How the freshness notice works
- This page is statically generated when the site is built, so the notice above reflects the report’s age at build time rather than at the moment you load it. The backstop is the build itself: it fails once the evaluation is more than 120 days old, which is 30 days after the cadence date, so a deploy cannot carry a report that has gone stale past that window.