Introduction
Five tiers of Woohl components, installed with the shadcn CLI.
The Woohl design system is distributed as a shadcn registry. You install a component with one command and it lands in your repo as source you own — no package to version, no build step, nothing to keep in sync by hand.
bunx --bun shadcn@latest add @woohl/buttonWhy a registry rather than a package
Four apps share this system: the buyer storefront, the seller portal, the admin console, and this site. They are separate repositories on separate release cadences, and for a long time the system was propagated between them by copying a folder. That worked until the copies drifted — one repo fixed a bug locally, the next copy overwrote it, and the diff that was supposed to catch that had become too noisy to read.
A registry keeps the good part of copying (you own the source, you can edit it, nothing is hidden behind a version range) and removes the bad part: the CLI resolves dependencies, rewrites import paths, and installs the npm packages a component needs.
The five tiers
Each tier may depend on the ones above it and never the other way round.
| Tier | What lives there |
|---|---|
| Tokens & brand | Colour, type, spacing, motion, the four product themes, the logo |
| Primitives | cn, cva, structural glyphs, IconSlot, Separator, Skeleton |
| Components | Button, Input, Badge, Avatar, Switch, Checkbox, Radio, Tooltip, Field, Card |
| Composites | Dialog, Drawer, Dropdown, Popover, Select, Tabs, Table, Pagination, Toast, Navigation |
| Blocks | ProductCard, SellerCard, CartItem, Price, Rating and the rest of the marketplace UI |
Installing a block pulls its whole chain. @woohl/product-card brings the
tokens, cn, Badge, Price, Rating and WishlistButton with it, because the CLI
follows registryDependencies for you.
Icons are yours, not ours
Nothing in this system ships an icon set. The buyer app draws Phosphor, the seller and admin portals draw Lucide, and a shared component that picked one would be wrong in two places out of three.
Components that need a mark you chose take it as a prop:
<Button icon={<Icon name="search" />}>Search</Button>The handful of marks a control draws as part of itself — a checkbox's tick, a
dialog's close cross, a select's caret — come from @woohl/glyphs and are not
an icon set you should reach into for anything else.