Component Scaffold Convention
Every component follows a standardized directory structure with co-located source, styles, client behavior, metadata, examples, and contract tests.
Context
With 48+ components to implement (USWDS-derived and custom), we needed a consistent structure that makes components self-contained and discoverable. Files that change together should live together.
Decision
Each component lives in src/components/flex-*/ with:
index.tsx— server-rendered JSX componentstyles.css— CSS using --flex-* tokens, cascade layer: blockclient.ts— custom element for interactive behavior (optional)meta.ts— catalog metadata (name, category, kind:'uswds-derived'or'custom')examples.tsx— variant examples for catalog and testscontract.ts/contract.tsx— contract spec (visual diff for USWDS-derived; render + a11y for custom)contract.test.ts— Playwright test that executes the contract
Variants use data-variant (mutually exclusive), sizing uses data-size, states use data-state, orthogonal modifiers use boolean data-* attributes. This aligns with peer web component libraries (Shoelace, Spectrum, shadcn). Internal child element scoping uses __ in class names.
A hardcoded registry (registry.ts) provides type-safe component lookup. A single register.ts bundles all interactive client scripts into dist/components.js, loaded on every page via a script tag in the Layout.
Alternatives considered
- Auto-discovery via glob — fragile, runtime errors from broken components, no type safety at build time
- Separate test directory — scatters related files across the tree, harder to maintain
- Per-component script loading — requires tracking which components are on each page, easy to forget
Consequences
- Adding a component requires: create directory with files, add to registry, add CSS import to styles.css, add client import to register.ts (if interactive)
- All component details are in one directory — no hunting across the tree
- Contract tests run via Playwright: USWDS-derived components diff against @uswds/uswds reference CSS; custom components verify render output and accessibility
- The pattern scales to 48+ components without changing the architecture
A digital services project by Flexion