Our homepage used to say every template was "accessible by default". When we went back to check that claim properly, it was mostly untrue. Two templates, Chit and Relay, had real accessibility work behind them. For the other eleven we had good intentions and no evidence, and when we ran a proper check, 11 of the 13 templates passed one check or none. So we audited all 13, fixed everything the audit found, and made the check part of the product.
What the audit checks
- axe-core 4.13 with every WCAG 2.0, 2.1 and 2.2 A and AA rule, plus axe's best-practice rules
- Every page type in the template, in each theme it ships (light and dark for 12 of the 13), at a 1280px desktop width and a 390px phone width
- Menus, drawers, panels, tabs and dialogs checked open, not just the page as it first loads
- Text contrast measured from the rendered pixels wherever axe gives up, which is most text over photos, gradients and gradient text
- A keyboard pass that tabs through the page and fails any focus stop with no visible focus indicator, or focus landing on something hidden
One check is one route or open state, in one theme, at one screen width. Across the 13 templates that comes to 526 checks, and all of them pass.
What it found
Almost nothing was exotic. The same few problems came up template after template, which is probably the most useful thing to know if you're auditing your own project:
One colour doing two jobs
A brand accent that looks great as a button fill under white text is often too light to use as small text on the page background, and vice versa. Most contrast failures were this. Tweaking one token only moves the failure somewhere else, so the fix was to split it: one token for fills, one for text. Agency Studio's red, Chit's flame red and Marginalia's terracotta all work this way now, and each template's CLAUDE.md says which token is which.
Closed menus you could still tab into
A slide-out drawer that is moved off-screen when closed is still in the page, so its links stay in the tab order. A keyboard user presses Tab and focus disappears into a menu they can't see. This turned up in mobile menus, a docs sidebar, a cart drawer and two dashboard detail panels. The fix: closed panels are inert, focus moves into a panel when it opens, Escape closes it, and focus goes back to the button that opened it.
Text over photos
axe can't work out the contrast of text sitting on a photo or a gradient, so it marks it for review instead of failing it. Measuring the rendered pixels found real failures: index numbers over Stockwell's product photos, category labels over Fashion Storefront's images, and white text on Keel's gold gradient. These got darker scrims, small solid chips behind the text, or dark text instead of white.
Structure and motion
Missing h1s, skipped heading levels, pages with no main landmark, icon buttons with no name, and scroll-reveal sections that stayed invisible for anyone using reduced motion. Every template now respects the reduced-motion setting.
The bug our first pass missed
After the first round of fixes, every template passed. Then, while fixing lint findings in SaaS Dashboard Pro, we spotted something in a screenshot that the audit hadn't: in the light theme, the sidebar's section labels (Overview, Manage, Account) were invisible. The class on them pointed at a colour name that doesn't exist, so the labels inherited the page's dark text colour and sat on a sidebar of exactly the same colour.
Our check missed it for two reasons. axe treats text that is exactly the colour of its background as possibly hidden on purpose, so it doesn't fail it. And our pixel measurement looks for the rendered letters, but there were none to find. Now the check fails any text that matches its background. The same round of review found that SaaS Dashboard Pro's settings tabs and dialogs had never been opened by the check at all. Adding them took it from 40 checks to 62, and immediately caught a delete button that was unreadable in dark mode. A check only covers what it opens.
Every template, checked
| Template | Checks passed | What the check covers |
|---|---|---|
| Agency Studio | 22/22 | 5 page types, light and dark, phone and desktop, plus the open mobile menu |
| AI SaaS Landing | 40/40 | 10 page types including docs, blog and auth, light and dark, phone and desktop |
| AI Tools Directory | 58/58 | 13 page types, light and dark, phone and desktop, plus the mobile menu and search |
| Chit | 40/40 | 9 page types, dark and light, phone and desktop, plus the open ordering assistant |
| Creative Portfolio | 18/18 | 4 page types, light and dark, phone and desktop, plus the open mobile menu |
| DocsKit | 30/30 | 7 page types, light and dark, phone and desktop, plus the open docs drawer |
| Elite CRM | 46/46 | 9 page types, dark and light, phone and desktop, plus the mobile menu and the deal and contact panels |
| Fashion Storefront | 18/18 | 6 page types, phone and desktop (light-only template), plus the cart drawer, a missing-size error and a filled bag |
| Keel SaaS Starter Kit | 64/64 | 13 page types signed in and out, dark and light, phone and desktop, plus menus, command menu and assistant |
| Marginalia | 52/52 | 11 page types, light and dark, phone and desktop, plus both menus and an open citation |
| Relay | 36/36 | 6 page types signed in and out, dark and light, phone and desktop, plus menus, model picker and citations |
| SaaS Dashboard Pro | 62/62 | 9 page types, light and dark, phone and desktop, plus every settings tab, the mobile menu, the user detail panel, the invite panel and the delete dialog |
| Stockwell | 40/40 | 10 page types, light and dark, phone and desktop |
Why the check ships with the template
Get the free template, plus new drops
DocsKit Free lands in your inbox right away. New templates after that.
An audit is a snapshot. The day you change the brand colour, or your coding agent adds a settings page, the snapshot is out of date. So the audit isn't a report we ran once and kept: it's the same script every template ships as npm run test:a11y, and the routes and open states it checks live in a plain config file next to package.json:
// a11y.config.mjs (SaaS Dashboard Pro, trimmed)
export default {
routes: ["/login", "/dashboard", "/dashboard/users", "/dashboard/settings"],
themes: { storageKey: "theme", values: ["light", "dark"] },
states: [
{
name: "delete account dialog open",
route: "/dashboard/settings",
steps: [
{ click: 'button:has-text("Security")' },
{ click: 'button:has-text("Delete Account")' },
],
},
],
};Run it after a build and it prints one line per check, then exits non-zero if anything fails, so it works in CI as is:
$ npm run build && npm run test:a11y
pass / (desktop light) (15 focus stops)
pass /about (desktop light) (27 focus stops)
pass /services (desktop light) (8 focus stops)
...
pass / [mobile menu open] (mobile dark)
22/22 checks passed (axe-core 4.13.0, wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa, best-practice)Each template's CLAUDE.md, AGENTS.md and .cursorrules now has an Accessibility section too: run the check before finishing, which token to use for text and which for fills, and how closed panels must behave. When you add a route or a dialog, you add it to the config, and the check covers it from then on.
What it doesn't cover
Automated checks catch a lot, but not everything. Whether alt text actually describes the image, whether the reading order makes sense, and how a screen reader announces a custom widget still need a person. These templates have not been through a full manual screen reader audit, and we don't claim they have. What we do claim is narrower and checkable: every page type, in every theme, at both widths, with its menus and dialogs open, passes WCAG 2.2 AA automated checks, and you can run the same check yourself.
Every template's results, coverage and fixes are on one page, and each template's page links straight to its own row.
See every template's accessibility results.
View accessibility resultsBrowse the full catalog.
Browse templates