An accessible, tested React component library. Headless behavior from Radix, themeable through CSS variables, shipped as tree-shakeable ESM + CJS with types.
Built as the shared foundation for my other projects — every component is keyboard-accessible, unit- and a11y-tested (jest-axe), and documented in Storybook.
Most of my apps re-implemented the same primitives, sometimes inaccessibly. Fathom UI fixes that once: a small set of composable, WCAG-minded components with a single theming contract, so the apps that consume it inherit correct focus behavior, ARIA wiring, and dark mode for free.
npm install @engraya/fathom-uiImport the stylesheet once (it defines the design tokens and component styles):
import "@engraya/fathom-ui/styles.css";
import { Button } from "@engraya/fathom-ui";
export function Example() {
return <Button variant="primary">Save</Button>;
}Tokens are CSS custom properties. Light is the default; dark applies automatically from the OS preference, and you can force either with data-theme on the root:
<html data-theme="dark"> ... </html>Override any token in your own CSS:
:root {
--fathom-primary: #0d7d72;
--fathom-radius: 0.75rem;
}| Component | Notes |
|---|---|
Button |
variant (primary/secondary/ghost/danger) · size (sm/md/lg) · asChild polymorphism via Radix Slot |
Input |
invalid toggles aria-invalid and error styling |
Badge |
variant (neutral/success/warning/danger/info) |
Dialog |
Radix-backed: focus trap, Esc to close, scroll lock, required accessible title |
Tabs |
Radix-backed: roving tabindex, arrow-key navigation, correct tablist/tab/tabpanel ARIA |
Switch |
role="switch", controlled or uncontrolled, keyboard-operable |
Tooltip |
Radix-backed: shows on hover/focus, portaled, positioned; provider included |
import { Dialog, DialogTrigger, DialogClose, DialogContent, Button } from "@engraya/fathom-ui";
<Dialog>
<DialogTrigger asChild><Button>Delete</Button></DialogTrigger>
<DialogContent title="Delete project?" description="This cannot be undone.">
<DialogClose asChild><Button variant="danger">Confirm</Button></DialogClose>
</DialogContent>
</Dialog>npm install
npm run storybook # component workshop at :6006
npm run test # Vitest + Testing Library + jest-axe
npm run test:coverage
npm run typecheck
npm run build # tsup -> dist (ESM + CJS + d.ts + styles.css)Every component has unit/interaction tests and an automated accessibility assertion via jest-axe (expect(await axe(container)).toHaveNoViolations()). Behavior that must be keyboard-operable (dialog open/close, focus) is tested with @testing-library/user-event.
Versioning is managed with Changesets:
npx changeset # describe the change
npx changeset version # bump + changelog
npm run build && npm run releaseMIT © Ahmad Yakubu Ahmad