The Reva layer
How the design system extends shadcn/ui with structured styling and layout primitives.
What this adds to shadcn
shadcn/ui gives you copy-paste components and Tailwind CSS. You own the code, you customize everything. This is great for speed, but it's intentionally unopinionated — no layout primitives, no structure beyond what you bring yourself.
This design system adds two things on top of shadcn:
- Layout & typography primitives —
Flex,Stack,Grid,Container,Text,Heading. Typed props instead of raw<div className="flex ...">. - AI agent rules — AGENTS.md and Cursor rules that enforce these patterns when building with AI.
Everything stays in the shadcn ecosystem. The CLI, blocks, registry, and templates all work. These additions are purely additive.
Layout Primitives
Why not just Tailwind classes?
Writing <div className="flex items-center justify-between gap-4"> works, but:
- It's verbose and error-prone
- No type safety on values
- AI agents generate inconsistent patterns
- Responsive direction changes need manual breakpoint classes
Layout primitives give you typed props that map to Tailwind classes:
{/* Before */}
<div className="flex flex-col gap-4 md:flex-row">...</div>
{/* After */}
<Flex direction={{ default: "column", md: "row" }} gap={4}>...</Flex>Component overview
| Component | Purpose | Default element |
|---|---|---|
Flex | Un-opinionated flex container. No default gap or alignment. | <div> |
Stack | Opinionated flex wrapper. Default gap-2, vertical direction, cross-axis defaults. | <div> |
HStack | Stack with direction="horizontal" | <div> |
VStack | Stack with direction="vertical" | <div> |
Grid | CSS Grid with responsive columns/rows | <div> |
Container | Max-width wrapper with centering and responsive padding | <div> |
Stack vs Flex
- Stack uses friendly direction names:
"vertical"/"horizontal". Has default gap (2), default alignment (vertical → stretch, horizontal → start). Use for everyday stacking. - Flex uses CSS-native direction names:
"row"/"column". No defaults. Use when you need full control, responsive direction, or reverse.
Stack is implemented as a thin wrapper around Flex.
Responsive props
Flex, Stack, and Grid accept responsive objects for direction/columns:
<Grid columns={{ default: 1, md: 2, lg: 3 }} gap={4}>
<Stack direction={{ default: "vertical", md: "horizontal" }} gap={4}>
<Flex direction={{ default: "column", md: "row" }} gap={4}>Breakpoints: default (base), sm, md, lg, xl, 2xl.
Typography Primitives
| Component | Purpose | Default element | Default font |
|---|---|---|---|
Text | Body text, labels, captions | <p> | Inter Variable (font-sans) |
Heading | Section headings and display type | <h2> | Inter Tight (font-heading) |
Both are polymorphic via the as prop. Heading accepts h1–h6 plus non-heading tags (p, span, div, strong, em, small, mark, label, figcaption, blockquote, legend) — useful when heading typography is wanted without a heading landmark (e.g. display numbers in a card with as="p").
Heading auto-scales its font size from the heading level (h1 → 4xl, h2 → 3xl, etc.); for non-heading as values it falls back to xl. You can override with the size prop. Default weight is semibold, default leading is tight.
Theme & Tokens
The theme system is CSS-native (Tailwind v4 — no tailwind.config.ts). Tokens aren't authored here — they come from @reva/design-tokens, the platform-agnostic source of truth: W3C DTCG JSON compiles through Style Dictionary to web CSS, a flat TypeScript table, and a Figma variables manifest, so web, mobile, and Figma stay in sync.
@reva/ui layers three CSS files over those token outputs:
| File | Purpose |
|---|---|
color-palettes.css | Imports the package foundation (OKLCH ramps + scales), pins the web breakpoint / radius / font-stack scale, and carries the Tailwind-palette overrides/disables plus brand-1–brand-5 |
tokens.css | Imports color-palettes.css plus the package semantic layer (intent families, light/dark modes) |
theme.css | Consumer entry point — imports tokens.css, adds animations, custom variants, squircle corners, and base styles |
Consumer apps import @reva/ui/theme.css and get everything. See the Design tokens section for the full token reference.
Font system
| Token | Font | Usage |
|---|---|---|
--font-sans | Inter Variable | Body text, UI labels |
--font-heading | Inter Tight Variable | Headings |
--font-mono | Geist Mono Variable | Code |
--font-brand | Playfair Display Variable | Brand/marketing |
Where this deviates from shadcn
| Area | shadcn default | This system | Why |
|---|---|---|---|
| Button sizes | sm, default, lg, icon | xs, sm, default, lg (no icon — use IconButton) | Cleaner separation of text vs icon buttons |
| Semantic intents | destructive only | destructive + constructive on Button; destructive / constructive / warning / caution / info on Badge | Named intents over arbitrary colour utilities |
| Layout | Raw div + Tailwind | Flex, Stack, Grid, Container primitives | Type safety, consistency, AI guidance |
| Typography | Raw p/h* + Tailwind | Text, Heading primitives | Consistent sizing, font, color |
| Red color scale | Tailwind default | Mulberry (custom OKLCH) | Brand-aligned warm red |
| Green color scale | Tailwind default | Fern (custom OKLCH) | Brand-aligned green |
| Yellow color scale | Tailwind default | Gold (custom OKLCH) | Brand-aligned warm yellow |
| Disabled palettes | All available | 11 palettes disabled (lime, fuchsia, etc.) | Reduce noise, enforce brand palette |
| Brand colors | None | brand-1 through brand-5 | White-label ready |
| Disabled cursor | pointer-events-none (no visible affordance) | pointer-events-none + cursor-not-allowed on form controls (Input, Select, etc.) | Visible disabled state on inputs; buttons follow shadcn |
| Corner shape | Plain border-radius | Progressive squircle (superellipse) corners via CSS corner-shape where supported | Softer corners on modern browsers; pixel-identical fallback elsewhere |
Working with AI agents
AGENTS.md and Cursor rules enforce these conventions:
- Use
Text/Headinginstead of raw<p>/<h*>with className - Use
Stack/Flex/Grid/Containerinstead of<div className="flex ..."> - Use a semantic intent variant (
destructive,constructive,warning,caution,info) instead of ad-hoc color classes on Badge or Button - After installing a shadcn component, refactor its internals to use layout primitives
AI agents following these rules will produce consistent, structured output that aligns with the system's conventions.