Component Library
Build Order (with validation gates)
- Tokens published → verify with
rg -- '--color-' dist/tokens.css. - Layout primitives (
Box,Stack,Flex,Grid) → verify token consumption:rg "#[0-9a-fA-F]{6}" src/primitives/exits empty. - Typography (
Heading,Text,Label). - Button → run a11y audit before proceeding:
npx test-storybook --url http://127.0.0.1:6006zero violations. - Form atoms (
Input,Select,Checkbox,Radio) → keyboard nav test passes. - Containers (
Card,Modal,Drawer) → focus-trap test passes for Modal. - Data (
Table,List) → axe-core audit on rendered story. - Nav (
Nav,Tabs,Breadcrumbs).
Gate per tier: a11y audit clean + visual regression reviewed before next tier.
Compound Component (Select)
import { createContext, useContext, useState, ReactNode } from "react";
type Ctx = { value: string | null; setValue: (v: string) => void };
const SelectCtx = createContext<Ctx | null>(null);
export function Select({ children, defaultValue = null }:
{ children: ReactNode; defaultValue?: string | null }) {
const [value, setValue] = useState<string | null>(defaultValue);
return (
<SelectCtx.Provider value={{ value, setValue }}>
<div role="listbox" className="rounded-md border">{children}</div>
</SelectCtx.Provider>
);
}
Select.Option = function Option({ value, children }:
{ value: string; children: ReactNode }) {
const ctx = useContext(SelectCtx)!;
const selected = ctx.value === value;
return (
<button role="option" aria-selected={selected}
=> ctx.setValue(value)}
className={selected ? "bg-[var(--color-brand-500)] text-white" : ""}>
{children}
</button>
);
};
CVA Variant Definition
import { cva } from "class-variance-authority";
export const button = cva("inline-flex items-center font-medium rounded-md", {
variants: {
intent: { primary: "bg-primary text-white",
outline: "border-2 border-primary text-primary",
ghost: "text-primary hover:bg-primary/10" },
size: { sm: "h-8 px-3 text-sm", md: "h-10 px-4", lg: "h-12 px-6 text-lg" }
},
compoundVariants: [
{ intent: "ghost", size: "sm", class: "underline-offset-2" }
],
defaultVariants: { intent: "primary", size: "md" }
});
Storybook Story (Button.stories.tsx)
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
const meta: Meta<typeof Button> = {
component: Button,
args: { children: "Click me" },
argTypes: { intent: { control: "select", options: ["primary","outline","ghost"] },
size: { control: "select", options: ["sm","md","lg"] } },
parameters: { a11y: { config: { rules: [{ id: "color-contrast", enabled: true }] } } }
};
export default meta;
export const Primary: StoryObj<typeof Button> = {};
export const Disabled: StoryObj<typeof Button> = { args: { disabled: true } };
export const Loading: StoryObj<typeof Button> = { args: { children: "Loading…" } };
Playwright Visual Regression (tests/visual/button.spec.ts)
import { test, expect } from "@playwright/test";
const stories = ["primary", "disabled", "loading"];
for (const id of stories) {
test(`Button ${id} visual`, async ({ page }) => {
await page.goto(`http://127.0.0.1:6006/iframe.html?id=button--${id}`);
await expect(page.locator("#storybook-root"))
.toHaveScreenshot(`button-${id}.png`, { maxDiffPixelRatio: 0.01 });
});
}
Run: npx playwright test tests/visual/ --update-snapshots on intentional changes; fail-block on diffs in CI.
Accessibility Audit (per-component gate)
# Headless axe-core on every Storybook story
npx test-storybook --url http://127.0.0.1:6006 --maxWorkers=2
# Or one-off:
npx @axe-core/cli http://127.0.0.1:6006/iframe.html?id=button--primary
Pass: zero serious/critical violations. Track moderate violations in issue tracker.
References
- Composition Patterns — compound, headless, polymorphic, refs
- Variant Systems — CVA, compound variants, type-safe APIs
- State Management — five UI states table, a11y requirements table, state machines
Next Steps
- Design System Creation: Ensure the library aligns with the broader design system governance
- Design Tokens: Consume tokens correctly and contribute component tokens back to the system
- Frontend Components: Implementation-specific guidance for React, Vue, and Svelte components
- Accessibility Audit: Validate component accessibility with automated and manual testing