ShadCN Primitives + Product Wrappers
Intent
Keep ShadCN primitives stable and token-normalized in src/components/ui, and express product typography/semantics in wrappers in src/components/typography or src/components/elements.
Touch vs wrap
Touch the primitive when:
- It uses non-system tokens (
text-sm,rounded-md,gap-4, default ShadCN colors). - It is reused widely and needs consistent defaults.
- Fixing it once prevents repeated call-site overrides.
Wrap the primitive when:
- You need semantic intent (eyebrow, label, badge copy, product defaults).
- Typography should be driven by
TextortextVariants. - The component is a composed pattern (Badge + Text, Button + Icon + Label).
Workflow
- Import the ShadCN component into
src/components/ui. - Normalize tokens using
tailwind-shadcn-adaptation(typography/spacing/radius) and class sorting. - Keep primitives agnostic: layout, shape, state, interaction only. No product semantics.
- Add a wrapper in
src/components/typographyorsrc/components/elements:- Compose the primitive with
TextortextVariants. - Use
asChild/Slot patterns to avoid coupling. - Provide opinionated defaults for product usage.
- Compose the primitive with
Cross-skill handoff
- If you're adapting a new primitive, read
tailwind-shadcn-adaptationfirst. - If you see repeated typography overrides in call sites, create a wrapper here.
Anti-patterns
- Adding typography intent props to
uiprimitives. - Fixing typography in multiple call sites instead of a wrapper.
- Mixing product semantics into
src/components/ui.
Example
Badgenormalized once insrc/components/ui/badge.tsx.EyebrowBadgeinsrc/components/typographycomposesBadge+Text intent="eyebrow".
Converted and distributed by TomeVault — claim your Tome and manage your conversions.