Practical rules for building with RDS consistently.
Use the three-font system intentionally:
font-display (Instrument Serif) — hero headings, large display text only. Don’t use for body copy.font-mono (DM Mono) — code blocks, labels, tags, timestamps, numeric data.font-sans (DM Sans) — everything else: body, UI text, captions.Type scale usage:
text-4xl / text-5xl — page heroes onlytext-2xl / text-3xl — section headingstext-lg — subheadings, card titlestext-sm — secondary text, metadata, captionstext-xs — badges, tags, helper textRDS uses a single ink color at varying opacities for hierarchy:
text-foreground — primary text (full ink)text-muted-foreground — secondary text (~60% opacity)text-muted-foreground/60 — disabled / hint textAvoid introducing custom colors. Use semantic tokens: bg-background, bg-muted, bg-card, border, ring.
For destructive actions: text-destructive, bg-destructive.
Use the Tailwind spacing scale. Prefer multiples of 4:
gap-2 (8px) — tight groups (icon + label)gap-4 (16px) — form fields, card internalsgap-6 (24px) — section contentgap-8 (32px) — between major sectionsMax content width: 1140px (max-w-screen-xl), padding: px-8 (32px).
asChild on trigger components when passing a custom element (e.g. Next.js Link inside Button)id / htmlForFormField > FormItem > FormControl when using React Hook Formcn() utility for conditional classNames — never string concatenationaria-label with the timestamp includedprefers-reduced-motion for waveform animations| Do | Don’t |
|---|---|
Use font-mono for timestamps and numeric data | Use serif fonts for body copy |
| Use semantic color tokens | Hardcode hex values in className |
Use asChild to forward props to custom elements | Wrap components in unnecessary divs |
| Use Sonner (toast) for notifications | Use the legacy Toast component |
| Show skeleton while content loads | Show empty space or layout shift |