How to build UI in this repo — compose classes with `cn`, use the CSS-variable design tokens (never raw hex/px), reuse the typographic and `ui/` primitives, and define variants with `cva` like the existing components. Use whenever writing or editing a component, styling JSX, or reaching for a primitive that does not yet exist.
Instrucciones de origen · Vista previa de solo lectura
name
design-system
description
How to build UI in this repo — compose classes with `cn`, use the CSS-variable design tokens (never raw hex/px), reuse the typographic and `ui/` primitives, and define variants with `cva` like the existing components. Use whenever writing or editing a component, styling JSX, or reaching for a primitive that does not yet exist.
Design system
This is a TanStack Start + Tailwind v4 site with a handrolled component layer and CSS-variable design tokens. There is no shadcn, no Base UI/Radix, no component CLI — primitives are written by hand in this repo. Stay inside the system: compose with cn, use tokens, reuse the existing primitives, and define variants with cva exactly like the components already here.
Every component that accepts className merges it last: cn(..., className) — so callers can override.
cn resolves Tailwind conflicts via tailwind-merge; rely on it instead of hand-written conditional strings.
See the clean-jsx-no-inline-ternaries skill for the branching rules.
Use design tokens, not raw values
Colors, fills, strokes, borders, and shadows are CSS variables exposed as Tailwind utilities via the @theme inline block in colors.css. Never hardcode #fff, rgb(...), or a palette utility like bg-neutral-900 for UI chrome.
SVG: fill-primary|secondary|tertiary|quaternary|inverted, stroke-primary|secondary|tertiary — prefer these over style={{ fill: "var(--…)" }} for icons that should track theme text colors
Shadows: shadow-muted, shadow-emphasis
If a needed token does not exist, add it to colors.css / text.css (under @theme inline) rather than hardcoding the value in JSX.
Exception: literal colors are fine for content artwork that is intentionally not theme-driven — e.g. the writing icon palette (src/features/writing/lib/icon-svg.ts). That is illustration, not UI chrome. UI chrome always uses tokens.
Reuse the primitives — don't re-style raw elements
Body / captions → Body, Body2 from @/components/design-system/body.
Inline code → Code.
Buttons, inputs, toggles, tooltips, links → import from @/components/ui/*. Use the Link from @/components/ui/link for navigation (it picks TanStack Router vs. plain <a> automatically).
Variants: use cva, mirror button.tsx
When a component's classes branch on discrete options (variant / size / state), define them with class-variance-authority, the same shape as button.tsx and heading.tsx:
Do not invent a parallel variant pattern, and do not chain ternaries to build class strings.
Adding a missing primitive (handrolled — there is no CLI)
If you need a primitive that isn't in src/components/ui:
Confirm it's missing — read src/components/ui first; reuse/extend if something close exists.
Match the local style — study button.tsx: cva for variants, cn(..., className) merge, forwardRef where a ref is useful (see link.tsx), tokens for every color.
Write it in src/components/ui/<name>.tsx by hand. Keep it small and composable; expose cva variants only if there are real variants.
Use tokens and cn throughout — no raw colors, no inline style for what Tailwind expresses.
Only reach for a third-party headless lib if the primitive is genuinely complex (focus trapping, positioning) and nothing comparable exists — match what's already installed first.
Icons
Use the central-icons package — import { IconChevronLeft } from "central-icons/IconChevronLeft". Not lucide. Size and color them with the surrounding Tailwind classes (size-4, text-tertiary / fill-*); don't pass width/height/size props.
Don'ts
No inline style={{}} for anything Tailwind can express. (Computed/animated values written imperatively are the exception.)
No raw color, radius, or shadow values for UI chrome (#0a0a0a, border-[1px] border-[#eee]). Use tokens.
No new "wrapper" components that only re-export a ui/ primitive.
No new variant systems — reuse cva exactly like button.tsx.
Don't re-style raw <h1>/<p>/<button> when a primitive exists.
Quick checklist before committing UI changes
Every className runs through cn(...), with className merged last.
Every UI color/border/shadow comes from a token (literal colors only for content artwork).
Headings/body/buttons/links use the existing primitives, not ad-hoc styled elements.
Any variants use cva, structured like button.tsx.
New tokens were added to colors.css / text.css, not hardcoded in JSX.