Skip to main content

The SDK

Design system components

Scanning a design system’s components, story by story.

Scanning a design system's components

A design system's defects enter every product that uses it — a button with no name, a focus ring at 1.9:1, a 36px target — and are cheapest to find before any page exists. If you have a Storybook, each story can be scanned as a component surface: one option, one spec file, no story edits.

ts
// playwright.config.ts
export default withSetsquare(
  defineConfig({
    use: { baseURL: 'http://127.0.0.1:6006/' },
    webServer: {
      command: 'npx http-server storybook-static -p 6006',
      url: 'http://127.0.0.1:6006/index.json',
    },
  }),
  { scope: 'component' },
);
ts
// stories.spec.ts
import { test } from '@playwright/test';
import { storybookStories } from '@setsquare/review-sdk';

for (const story of storybookStories('storybook-static/index.json')) {
  test(`${story.title} / ${story.name}`, async ({ page }) => {
    await page.goto(story.path);
  });
}

storybookStories() reads the same index.json Storybook's own test runner reads in its index mode, and skips a story without the test tag, as that runner does. Each story becomes a surface named by its story id. Storybook's test runner is Jest driving a browser, not Playwright Test, so this is a Playwright spec beside it rather than a wrapper around it.

What scope: 'component' changes, and nothing else:

  • Eight criteria read as not applying on a component, each with its reason: 2.4.1, 2.4.2, 2.4.5, 2.4.8, 3.1.1, 3.2.3, 3.2.4 and 3.2.6. What each is about is a page or a set of pages — a title, a default language, a way to reach a page, navigation repeated across pages — and a component rendered alone has none of those to have or to lack. WCAG itself says nothing about components; the reasons rest on its definitions of web page and set of web pages. The criteria that constrain content on a page (flashing, autoplaying audio, focus order) stay in scope, because a component that fails them fails every page that renders it.
  • Rules that read only the host document are not reported: its <meta> viewport and refresh, and aria-hidden on its <body>. That document is Storybook's preview, the same for every story. Two of them are the only rule this engine has for their criterion, so 1.4.4 and 2.2.1 read untested on a component, and the report says why.
  • Every report says component evidence is not page evidence. WCAG defines conformance for full pages only (5.2.2). A design system whose components all pass can still ship an inaccessible page.

Everything else runs as it does on a page: axe, the rule pack, the driven checks at each test's teardown, and clickProbe if you turn it on.

What this adds to @storybook/addon-a11y, which already runs axe on every story: the checks axe has no rule for (focus appearance, 2.5.5 target size with the click probe, non-text contrast, keyboard traps), text spacing applied and measured rather than read from inline styles, the distinction between "we checked and it passed" and "we could not check", and the same envelope, report, findings.md and SARIF a product run produces — so one report shape runs from component to page. Running axe per story is not the new part.