@framework-cwf/ui
v0.2.6
Published
Accessibility-audited React Server-Component-friendly primitives (Button, Card, Modal, Drawer, Nav, Footer, FormField, Image, Link).
Downloads
262
Readme
@framework-cwf/ui
Twelve accessibility-audited React primitives that consume design tokens from
@framework-cwf/tokens. The full set is intentionally small — anything more
domain-specific lives in the consuming app, not the framework.
Installation
Published to GitHub Packages under the @framework-cwf scope. Consumers need an
.npmrc pointing the scope at the GitHub Packages registry plus an auth token:
@framework-cwf:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}pnpm add @framework-cwf/uiWhat's here
| Primitive | Bundle role | Notes |
| --------------- | ---------------- | ------------------------------------------------------ |
| Link | Server Component | Wraps next/link. Internal vs external auto-detect. |
| Footer | Server Component | Composition: brand + sections + bottom strip. |
| SectionHeader | Server Component | Eyebrow + headline + description + CTA cluster. |
| Card | Server Component | Padding/radius/elevation knobs from tokens. |
| Image | Server Component | Wraps next/image. alt is required at type level. |
| Button | Server Component | Plain button — variant + size only. |
| LoadingButton | Client Component | Adds loading spinner + aria-busy. |
| FormField | Server Component | Wires label + describedby + aria-invalid. |
| Input | Client Component | Standard text/email/tel/number. |
| Select | Client Component | Native <select> styled to brand. |
| Nav | Client Component | Mobile menu state + sticky variant. |
| Modal | Client Component | Portal + focus trap + Escape + click-outside. |
| Drawer | Client Component | Same contract as Modal; slides from left/right/bottom. |
Quick example
import { Button, Card, Link, SectionHeader } from "@framework-cwf/ui";
export default function Page() {
return (
<main>
<SectionHeader
eyebrow="Services"
headline="Cuts, colour, and care"
description="Premium grooming for every kind of day."
cta={
<Button variant="primary">
<Link href="/book">Book now</Link>
</Button>
}
/>
<Card padding="lg" radius="lg" elevation="md">
Card content here.
</Card>
</main>
);
}Bundle budget
The full client-side surface (the 6 'use client' primitives) currently
weighs ~1.5 kB gzipped, well under the 12 kB acceptance bar in
TASKS.md §T1.F.2. The bundle-size test in
src/bundle-size.test.ts enforces this on every
CI run; if it ever turns red because a new client component was added,
re-evaluate whether each "use client" directive earns its weight.
Accessibility
- Every primitive has a story in Storybook with
@storybook/addon-a11yenabled. The addon panel runs axe-core live for every story. - Every primitive also has a vitest a11y test that asserts zero axe-core violations on the rendered markup.
color-contrastandregionrules are disabled at the component level — the former can't run meaningfully in jsdom (no canvas), and the latter is a layout-level concern. Brand colour contrast is enforced at the design-token level in@framework-cwf/tokens.- Modal + Drawer ship a hand-rolled focus trap (
src/useFocusTrap.ts) that's ~60 lines and keeps the bundle thin —focus-trap-reactwould add ~3 kB on its own.
Stories
Stories live next to their components at
packages/ui/src/[Name]/[Name].stories.tsx. Storybook discovers them via the
glob in apps/storybook/.storybook/main.ts.
Each component ships at minimum the four stories the task spec calls out:
Default, Variants, FocusState, DarkMode.
The Storybook host doesn't run a Tailwind compilation step today — the
@framework-cwf/tokens brand fixture is materialised as plain CSS variables
in apps/storybook/.storybook/tokens.css
so reviewers see the colour values. Full Tailwind class compilation (so
bg-primary-600 resolves to the actual background) lands with apps/template
in T1.G.2.
