Skip to main content

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.

Conformance claim per surface, version 1, evaluated 16 September 2026. Claims use the five reporting values only.
Surface & evaluationConformance claimEvaluation methodKnown issues
thelatchkey.in marketing site, the free scanner and its result screensWCAG 2.2 Level AAEvaluated 18 September 2026Partially Supports
Read method for thelatchkey.in marketing site, the free scanner and its result screens

axe-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 2026Partially Supports
Read method for thelatchkey.in portal: websites, account settings, team and billing

FIRST 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 2026Partially Supports
Read method for thelatchkey.in/help help centre

Served 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 2026Partially Supports
Read method for thelatchkey.in/status status page

Re-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 yetNot Evaluated
Read method for The widget's own visitor interface, as shipped, in a Shadow DOM host on a fixture site

The 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 yetNot 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 yetNot 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 yetNot 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 yetNot Evaluated
Read method for PDF deliverables: the Remediation Report and the Evidence Pack

A 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

This page is a dated report about our own product. It is not a signed third-party audit and it is not a VPAT. Standard-edition reports for procurement (WCAG 2.2, Revised Section 508, EN 301 549) are human work we produce on request: ask us through the contact page.

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.

Published issues as of 16 September 2026: 4 open, 0 in progress and 3 fixed. Fixed issues remain visible.
IssueImpact & criterionWorkaroundOwner & targetStatus
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.

The widget's own visitor interface

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 2Open
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.

The widget's own visitor interface

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 2Open
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 2Open
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.

thelatchkey.in marketing site

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 2Open
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.

thelatchkey.in marketing site, thelatchkey.in portal

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 0FixedFixed 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.

thelatchkey.in marketing site, thelatchkey.in portal

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 0FixedFixed 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.

thelatchkey.in marketing site

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 0FixedFixed 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.

Not yet tested

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.

  • ACC-GAP-01 · Owner: accessibility-lead · Target: Phase 2

    What was not evaluated
    No assistive technology pass has been performed on any LatchKey surface. No NVDA, JAWS, VoiceOver, TalkBack, Dragon, ZoomText or braille display session has been run against the marketing site, the portal, the widget interface, the statement pages or the PDFs.
    Why not
    The environment this product is currently built in cannot run a screen reader, and no session has been run outside it either. An assistive technology matrix is planned for Phase 2 and is not in place today.
    So do not conclude
    Do not read any claim on this page as saying a screen reader user can complete a task. The evidence behind every claim here is automated checking plus source review, and automated checking reaches roughly one third of the WCAG 2.2 AA criteria. This is the same limit we publish about our own product on /limitations, applied to ourselves.

    Surfaces affected: thelatchkey.in marketing site, thelatchkey.in portal, thelatchkey.in/help help centre, thelatchkey.in/status status page, The widget's own visitor interface, The embedded Shopify app at /embedded/shopify and the Wix dashboard page at /embedded/wix, Published accessibility statement pages at /statement/{publicId}, PDF deliverables.

  • ACC-GAP-02 · Owner: accessibility-lead · Target: Phase 2

    What was not evaluated
    No manual keyboard-only traversal has been recorded for any surface. Focus order, focus return after a dialog closes, keyboard operability of custom components and escape from a focus trap have not been tested by a person.
    Why not
    The self-audit script drives the interface through role-based queries in order to reach interactive states for the axe pass. Reaching a state by asking the browser for a button by its role is not the same as arriving at it with the Tab key, and it asserts nothing about the order or the return path.
    So do not conclude
    Do not read the absence of keyboard issues in the table above as evidence that keyboard journeys work. Nothing in this evaluation would have found a broken one.

    Surfaces affected: thelatchkey.in marketing site, thelatchkey.in portal, thelatchkey.in/help help centre, thelatchkey.in/status status page, The widget's own visitor interface, The embedded Shopify app at /embedded/shopify and the Wix dashboard page at /embedded/wix.

  • ACC-GAP-03 · Owner: growth-eng · Target: Phase 2

    What was not evaluated
    Ten designed page-states are named by the self-audit and were not reached by it. Six are the scan result screens (barriers found, needs review, clean, failed, expired, in progress), which need a fixture scan id the run was not given. Three are the desktop mega-menu at widths where the desktop navigation does not render, and one is the mobile menu at a width where the hamburger does not render.
    Why not
    The result screens need seeded fixtures the audit run did not receive. The remaining four are the audit asking for a control that does not exist at that viewport, which is the harness describing its own coverage honestly rather than a product defect. CORRECTED on 22 September 2026: this entry previously counted fourteen states and put the scan landing page inline validation error, at all four widths, in that second group. That attribution was wrong. The address field is viewport-independent; it was absent because workerHealthy() returned false and /scan renders a maintenance banner in place of the entire form when it does, so the run had been made without a worker. The four states are now audited, either against a running worker or against the loopback health stub `pnpm audit:self -- --worker-health-stub` starts, and this page no longer claims a viewport reason for them.
    So do not conclude
    Do not read 72 page-states checked with no violations as covering the result screens. The result screens are where a score is shown and are the most consequential states on the site, and they were not audited in this run.

    Surfaces affected: thelatchkey.in marketing site, thelatchkey.in/status status page.

  • ACC-GAP-04 · Owner: platform-eng · Target: Phase 3

    What was not evaluated
    The widget interface as mounted in a Shadow DOM host, and the print surface, have never had an axe-core pass. The portal has: it was audited on 16 September 2026 and has come off this gap.
    Why not
    The route list in scripts/audit-self.mjs was written for the Phase 0 marketing exit criterion. The portal half of this gap was closed by pnpm seed:portal-fixtures, which mints the signed-in session the script had always been able to accept -- so the stated reason for the portal, that no session could be made, turned out to be wrong rather than hard. What is left needs a fixture host page that mounts the widget and a print-media pass, neither of which the script sets up.
    So do not conclude
    Do not read this page as a statement about the widget interface or the print surface. Both are recorded as Not Evaluated for that reason. The portal is no longer among them.

    Surfaces affected: The widget's own visitor interface, PDF deliverables.

  • ACC-GAP-05 · Owner: portal-eng · Target: Phase 2

    What was not evaluated
    The published statement pages at /statement/{publicId} have not been evaluated.
    Why not
    The route does not exist yet. A request to it on the current build returns 404.
    So do not conclude
    Do not read the row as a claim held in reserve. There is no surface, so there is no claim.

    Surfaces affected: Published accessibility statement pages at /statement/{publicId}.

  • ACC-GAP-06 · Owner: reports-eng · Target: Phase 2

    What was not evaluated
    No tagged-PDF check has been run against any generated document, and no PDF/UA checker has been chosen.
    Why not
    A tagged PDF is a different conformance question from a web page: reading order, tags, artifacts, table headers and alternative text live in the document structure, and a web checker sees none of it. No such tool is in this repository.
    So do not conclude
    Do not assume the PDF inherits the web report's evaluation. It does not, and the two are evaluated separately for that reason.

    Surfaces affected: PDF deliverables.

  • ACC-GAP-07 · Owner: integrations-eng · Target: Phase 4

    What was not evaluated
    The embedded Shopify app has not been driven inside the Shopify admin frame, nor the Wix dashboard page inside the Wix dashboard, and no keyboard-only or screen reader pass has been run against either there or against the connect pages they open.
    Why not
    There is no Shopify Partner account or development store to install the app on, and no Wix developer account or test site, so neither admin frame, nor App Bridge, nor the theme editor the Shopify app links to can be loaded here. The self-audit renders both pages unframed, with the session answered from fixture views (and App Bridge stubbed for Shopify), which is as far as a build can reach.
    So do not conclude
    Do not read an axe run over the unframed pages as saying either app works inside its platform’s admin. Entering and leaving the frame, where focus lands when the app opens the LatchKey tab or the theme editor, and the admin controls around the app are exactly what that run cannot see.

    Surfaces affected: The embedded Shopify app at /embedded/shopify and the Wix dashboard page at /embedded/wix.

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.

The page states our own axe gate audits, and the states it reports as skipped, as of 16 September 2026.
Page or stateIn the gate todayWhat that means
Marketing homeAudited/ covers the hero, the scan form, the dark band and the footer, at every width the run uses.
Free scan landingAudited/scan as a visitor first sees it.
Scan landing, prefilledAuditedAn address arriving in the query string, which renders a different form state.
Scan landing, inline validation errorAudited, with a worker runningReached 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 openAudited from 1280 upThe 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 openAudited below 1280The 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.
LimitationsAudited/limitations, the page that carries the coverage argument.
The rest of the marketing pagesAudited/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 inAudited/login, including its form labelling.
Not-found pageAuditedAn 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 foundSkipped without a fixtureNeeds 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 reviewSkipped without a fixtureNeeds a completed scan with no violations and at least one check that needs a person.
Result screen: no automated failuresSkipped without a fixtureNeeds a completed scan with no violations and nothing to review.
Result screen: scan failedSkipped without a fixtureNeeds a scan row in the failed state.
Result screen: result expiredSkipped without a fixtureNeeds a scan past its expiry, or one whose screenshot has been purged.
Result screen: scan in progressSkipped without a fixtureNeeds a scan still queued or running.
Embedded Shopify app, every stateAudited 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 linkAudited/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 stateAudited 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 linkAudited/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 stateSkipped without a fixtureThe 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 drawerAudited from seeded rowsThe 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 issuesAudited from seeded rowsTwo 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 setAudited from seeded rowsThe 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 historyAudited from seeded rowsThe 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 tokensAudited from seeded rowsThe 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.
JourneysAudited from seeded rowsThe 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-resolveAudited from seeded rowsThe 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 integrationsAudited from seeded rowsThe 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 AppAudited from seeded rowsThe 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 stateAudited from seeded rowsAn 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)

    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.

  • ACC-006 · Fixed 7 September 2026

    2.5.8 Target Size (Minimum) (AA)

    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.

  • ACC-007 · Fixed 7 September 2026

    3.1.1 Language of Page (A)

    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.

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

How to tell us

Write to accessibility@thelatchkey.in, the first route on our contact page. It reaches the accessibility-lead role rather than a shared inbox, and it is the same address a visitor to a customer’s site can use.

You do not need to name a success criterion, and you do not need to be a customer. The page, what you were trying to do, and what your browser or assistive technology did instead is plenty. “The third link on the pricing page does nothing with a keyboard” is a good report.

Contact us about a barrier

What you can expect back

  • An acknowledgement, which we aim to send within two business days.
  • A substantive reply within five business days: whether we could reproduce it, and on what browser and assistive technology.
  • The success criterion we think it maps to, so you can disagree with us in the same vocabulary.
  • Whether it is going on the known-issues table above, who owns it, and what we intend to do next. If it does, you get the link to your own row.
  • If we are not going to fix it soon, that answer and the reason, rather than a closed ticket and silence.
What this document is not. This is our own accessibility statement and conformance report, not an audit signed by a third party, and not a VPAT. We produce those for customers as human work, and this page does not pretend to be one. Nothing here is legal advice, and a passing gate is not a determination that any site meets any legal standard. When the claims, the method or the issue list change, the version and the evaluation date at the top change with them.

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.

Version 1 · Evaluated 16 September 2026 · Next due 15 December 2026 · Target WCAG 2.2 Level AA · Owner: accessibility-lead · What the regulations require