Files
brain/web/STYLE_GUIDE.md
Andrew S Erwin b9721ff13f
release gate / check (push) Failing after 8m35s
initial commit on Federted servers
2026-10-19 16:23:55 -05:00

9.7 KiB

Layout

The root layout, app/layout.tsx, is the single place for app-wide providers and global infrastructure.

Do not remove any existing entries without a reason. Current infrastructure in the layout:

Entry Purpose
ThemeProvider Light/dark mode via next-themes
Toaster Global toast notifications via Sonner
ChunkLoadErrorHandler Required — prevents known ChunkLoadError race condition bug

Typography

Role Font Tailwind Class Default Usage
Body DM Sans font-sans All body text, labels, descriptions
Display Plus Jakarta Sans font-display Page titles, hero headings, section headers
Mono JetBrains Mono font-mono Code snippets, numeric data, IDs, timestamps

Size hierarchy: Use Tailwind's scale. Headings: text-4xl→text-3xl→text-2xl→text-xl. Body: text-base→text-sm. Captions: text-xs.

Always use tracking-tight on large headings (text-2xl and above).


Color System (Design Tokens)

All colors use CSS variables — never hardcode color values.

Token Purpose
background / foreground Page-level bg and text
card / card-foreground Card surfaces
primary / primary-foreground Brand buttons, links, accents
secondary / secondary-foreground Secondary buttons, subtle highlights
muted / muted-foreground Disabled states, helper text, subtle backgrounds
accent / accent-foreground Hover states, active nav items
destructive / destructive-foreground Errors, delete actions
border Borders and dividers
input Form input borders
ring Focus rings

Usage: bg-primary, text-muted-foreground, border-border, etc.


Spacing Scale

Based on an 8px grid. Use these CSS variables or Tailwind equivalents:

Token Value Tailwind
--spacing-xs 4px p-1, gap-1
--spacing-sm 8px p-2, gap-2
--spacing-md 16px p-4, gap-4
--spacing-lg 24px p-6, gap-6
--spacing-xl 32px p-8, gap-8
--spacing-2xl 48px p-12, gap-12
--spacing-3xl 64px p-16, gap-16

Vary spacing rhythm — don't use the same gap everywhere. Hero → large gap → content → medium gap → footer.


Shadow Scale

Token Default Usage
--shadow-sm Subtle card lift, input focus
--shadow-md Cards, dropdowns, popovers
--shadow-lg Modals, elevated panels

These are CSS variables only — use them directly in inline styles or custom CSS as var(--shadow-sm) etc. They are not mapped to Tailwind's shadow-* utilities.


Border Radius

Token Value Default Usage
--radius 0.625rem Default (buttons, inputs, cards)
--radius-sm calc(var(--radius) - 4px) Small elements (badges, chips)
--radius-lg calc(var(--radius) + 4px) Large containers, hero cards
--radius-full 9999px Avatars, pills, circular buttons

Animation Timing

Token Value Tailwind Class Default Usage
--duration-fast 150ms duration-fast Hover states, toggles
--duration-normal 250ms duration-normal Page transitions, reveals
--duration-slow 350ms duration-slow Complex animations, modals

Layout Components

The live app shell is @/components/app-shell (sidebar + top bar + main content, wired through app/(app)/layout.tsx); screens render their own headers inline. Login/signup use a centered card built directly in their route files. The generic components/layouts/* wrappers documented here previously were never imported and have been removed — reintroduce shared layout primitives only when a second consumer exists.


Animation Components — @/components/ui/animate

Component Default Usage Key Props
FadeIn Reveal content on scroll delay, duration
ScaleIn Pop-in effect delay
SlideIn Directional entrance from (top|bottom|left|right), delay
Stagger + StaggerItem Sequential reveal for lists/grids staggerDelay
HoverLift Lift effect on hover (cards, links) —
PressScale Press-down feedback for buttons —
SkeletonPulse Loading placeholder className (set width/height)

Pattern: Wrap page sections in FadeIn, list items in Stagger/StaggerItem, interactive cards in HoverLift.


UI Components — @/components/ui/

Core

Component Import Key Props
Button @/components/ui/button variant (default|secondary|outline|ghost|destructive|link|glass-dark|glass-light), size (default|xs|sm|lg|icon|icon-sm), loading (boolean). glass-dark: for dark/vivid backgrounds. glass-light: for light/pale backgrounds. Link: focus uses underline, not ring.
Badge @/components/ui/badge variant (default|secondary|outline|destructive)
Card @/components/ui/card variant (default|interactive|glass-dark|glass-dark-interactive|glass-light|glass-light-interactive|ghost). Composed: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter. Interactive: always wrap in <a> or <button> for keyboard access.
Separator @/components/ui/separator orientation (horizontal|vertical)

Forms

Component Import
Input @/components/ui/input
Textarea @/components/ui/textarea
Label @/components/ui/label
Select @/components/ui/select
Checkbox @/components/ui/checkbox
RadioGroup @/components/ui/radio-group
Switch @/components/ui/switch
Slider @/components/ui/slider
Calendar @/components/ui/calendar
DateRangePicker @/components/ui/date-range-picker
InputOTP @/components/ui/input-otp
Form @/components/ui/form (react-hook-form integration)

Navigation & Layout

Component Import
Tabs @/components/ui/tabs — Tabs, TabsList, TabsTrigger, TabsContent
Accordion @/components/ui/accordion — Accordion, AccordionItem, AccordionTrigger, AccordionContent
NavigationMenu @/components/ui/navigation-menu
Breadcrumb @/components/ui/breadcrumb
Pagination @/components/ui/pagination
Menubar @/components/ui/menubar
ScrollArea @/components/ui/scroll-area
Resizable @/components/ui/resizable

Overlays & Feedback

Component Import
Dialog @/components/ui/dialog
AlertDialog @/components/ui/alert-dialog
Sheet @/components/ui/sheet — side-panel overlay
Drawer @/components/ui/drawer — bottom sheet (great for mobile)
Popover @/components/ui/popover
Tooltip @/components/ui/tooltip
HoverCard @/components/ui/hover-card
ContextMenu @/components/ui/context-menu
DropdownMenu @/components/ui/dropdown-menu
Command @/components/ui/command — command palette / searchable list
Alert @/components/ui/alert
toast import { toast } from 'sonner' — toast.success(), toast.error()

Data Display

Component Import
Table @/components/ui/table — Table, TableHeader, TableBody, TableRow, TableHead, TableCell
Avatar @/components/ui/avatar
Progress @/components/ui/progress
Skeleton @/components/ui/skeleton
AspectRatio @/components/ui/aspect-ratio
Carousel @/components/ui/carousel

Misc

Component Import
Toggle @/components/ui/toggle
ToggleGroup @/components/ui/toggle-group
Collapsible @/components/ui/collapsible
ThemeToggle @/components/theme-toggle — light/dark mode switch

SSR / Hydration Safety — @/components/client-only, @/components/safe-format

Server-rendered HTML must match the client's first render. An automated SSR lint (eslint.ssr.config.mjs — do not delete) runs after every build and fails it on unsafe patterns. Use these primitives instead of hand-rolling fixes:

Primitive Import Use for
ClientOnly @/components/client-only Anything browser-only or non-deterministic: window/localStorage reads, live clocks, random values, third-party widgets. Pass a fallback sized like the content.
useMounted() @/components/client-only Hook variant when you need the boolean directly.
SafeDate / SafeTime @/components/safe-format Dates/times — formats with explicit locale + UTC so SSR matches client. localize re-renders in the visitor's timezone after mount. Props: date (Date|string|number), options? (Intl.DateTimeFormatOptions), locale?, localize?, className? — there is no format/value prop; e.g. <SafeDate date={post.createdAt} options={{ dateStyle: 'medium' }} />.
SafeNumber @/components/safe-format Numbers/currency, same guarantees. Props: value (number), options? (Intl.NumberFormatOptions), currency? (currency="USD"), locale?, localize?, className?.
Rules of thumb: never touch window/document at module scope; never seed useState with
Date.now()/Math.random()/new Date(); never call toLocaleString-style methods without
an explicit locale (and timeZone for dates).