A component registry for React and Tailwind CSS v4 — components install into your app as source files you own and edit, not as a package you depend on.
Every component styles against a semantic token contract (accent, bg, text, muted, …), so each app keeps its own palette and the components just work.
Adire (ah-DEE-ray) is the Yoruba craft of resist-dyed cloth: one pattern, a different indigo every time. Same idea here — one set of components, a different palette in every project.
Point your app's components.json at the registry:
Then install:
npx shadcn@latest add @adire/styles # once per new app — the stylesheet structure + token contract
npx shadcn@latest add @adire/button # pulls cn automaticallyFiles land in your source tree (src/styles/*.css, components/ui/base/button.tsx, lib/utils/cn.ts) and are yours from that moment — edit them to fit the project.
| Item | What it is |
|---|---|
styles |
The stylesheet structure — globals.css importing the active theme, overrides.css (project tweaks that survive theme switches), and the base, typography, forms, animations, utilities and components layers |
theme-lilac / theme-ocean / theme-ember / theme-slate |
Complete palettes (light + dark, matched shadows and glass) — pick one, or use it as the starting point for a custom palette |
| 27 base components | avatar, badge, bullet-list, button, checkbox, chip, dual-toggle, external-link, gradient, icon-badge, icon-button, image, input, label, link, overlay, progress-bar, radio, select, separator, skeleton, spinner, swatch-picker, switch, tag, textarea, theme-script |
| Blocks | accordion, alert, breadcrumbs, card, segmented-control, tabs, tabs-nav |
cn |
Class-name merge with Tailwind conflict resolution (clsx + tailwind-merge) |
Each component installs only its own file plus the code it genuinely imports — add @adire/button brings the button and cn, nothing else. Every axis is a fixed vocabulary (7 sizes, 10 radii, 7 tones, 5 interactive variants) rather than per-component invention, so components compose without surprises.
Every component that links internally — button, icon-button, card, breadcrumbs, tabs-nav — renders through link, and that file is the single place to switch:
// components/ui/base/link.tsx
import NextLink from "next/link";
const DEFAULT_LINK: ElementType = NextLink;One edit and the whole set does client-side navigation with prefetch, instead of the full page reload a plain <a> causes. Individual instances can still override it with linkAs. Offsite destinations use external-link, which stays a plain anchor — a router adds nothing there, and it hardens rel against tab-napping.
Components style against the semantic token names (--color-accent, --color-bg, …), never raw values — swap the values in themes.css per app and every component follows. Dark mode is token overrides under [data-theme="dark"]; components never branch on the theme.
Components are organized in tiers — base (primitives) → blocks (composed) → patterns (full sections) → layouts (page shells) — and install into matching folders. More components are added tier by tier.
Accessibility is part of "done" here, not a later pass, and it is enforced rather than intended:
- Anything interactive is a native control. Clicks live on
button,aandinput— never on adivwith a handler — so focus order, Enter and Space, and disabled states come from the platform instead of from someone remembering them.tabIndexappears only where an ARIA pattern requires it, such as the roving tabindex intabs. - Every interactive component has a story that operates it by keyboard alone, asserting focus and activation by key. Those stories run as tests, so a component that stops being keyboard-operable fails the build.
- Axe runs on every story at error level (
a11y: { test: "error" }), so a violation fails the test run. - No state rides on colour alone. Sorting says
aria-sort, the current page saysaria-current, a finished step says "completed" in words — and the contrast of every token pair is checked bypnpm check:contrastin all four themes. - RTL is a first-class direction: logical properties throughout, and only direction-neutral icons go unmirrored.
Tailwind CSS v4 and React. The registry ships no runtime package; per-component npm dependencies (like clsx) are installed by the CLI as needed.
This registry tracks my own component defaults, but issues and suggestions are welcome.
{ "registries": { "@adire": "https://raw.githubusercontent.com/Timonwa/adire-ui/main/r/{name}.json", }, }