Files
Reactive-Resume/DESIGN.md
T

13 KiB
Raw Blame History

version, name, description, colors, typography, rounded, spacing, motion
version name description colors typography rounded spacing motion
6.0.0 Reactive Resume · Desk & Paper A quiet, warm desk around a bright page. The resume is the only white, detailed object on screen; one moss-green accent marks the next action. Light and dark themes, with the page always white.
light dark paper stages
bg surface raised sunken line line-2 ink ink-2 ink-3 accent accent-hover on-accent accent-soft accent-text danger danger-soft danger-text warn warn-soft warn-text info-soft info-text
#F8F7F3 #FEFDFC #FFFFFF #F0EFEB #DFDEDA #C5C4BE #1C1B15 #4F4D47 #6D6C65 #337344 #206133 #F7FEF8 #DCF2DF #195C2E #BA3630 #FFE7E4 #A92321 #D29922 #FCEDCD #81520A #E0F1FF #1D5B92
bg surface raised sunken line line-2 ink ink-2 ink-3 accent accent-hover on-accent accent-soft accent-text danger danger-soft danger-text warn warn-soft warn-text info-soft info-text
#100F0C #171613 #1F1E1A #0B0A08 #2C2B27 #494843 #EFEEEB #BCBAB5 #979590 #6FC082 #83D494 #07150A #1A3520 #8FD89E #D9544B #47211D #FDA297 #E4B750 #3E2D10 #EFCC83 #192F46 #9DC9F7
#FFFFFF
saved applied screening interview offer closed
#908C7F #5590CC #00A0A6 #AF8433 #579F68 #C67067
display title sheet-title heading section-heading label body ui small caption mono
fontFamily fontSize lineHeight fontWeight letterSpacing
Newsreader 44px 48px 500 -0.01em
fontFamily fontSize lineHeight fontWeight
Newsreader 30px 36px 500
fontFamily fontSize lineHeight fontWeight
Newsreader 22px 28px 500
fontFamily fontSize lineHeight fontWeight
Hanken Grotesk 20px 28px 600
fontFamily fontSize lineHeight fontWeight
Hanken Grotesk 17px 24px 600
fontFamily fontSize lineHeight fontWeight
Hanken Grotesk 15px 22px 600
fontFamily fontSize lineHeight fontWeight
Hanken Grotesk 15px 24px 400
fontFamily fontSize lineHeight fontWeight
Hanken Grotesk 14px 20px 400
fontFamily fontSize lineHeight fontWeight
Hanken Grotesk 13px 18px 400
fontFamily fontSize lineHeight fontWeight
Hanken Grotesk 12px 16px 500
fontFamily fontSize lineHeight fontWeight
JetBrains Mono 12px 16px 500
sm md lg xl 2xl 3xl full
6px 8px 10px 12px 16px 18px 999px
4
8
12
16
24
32
48
64
quick standard emphasized easing exit
120ms 200ms 320ms cubic-bezier(0.2, 0.8, 0.2, 1) 70% of the entering duration

Overview

Reactive Resume's interface is a quiet, warm desk around a bright page. The resume or letter page is the only white, detailed object on screen; everything around it uses low-contrast warm neutrals, thin rules instead of boxes, and a single moss-green accent.

The full specification lives in the redesign handoff (design_handoff_reactive_resume_redesign/README.md, kept out of version control) and the milestone plan in REDESIGN_PLAN.md. This document records the rules the code follows.

Five principles decide most questions:

  1. The page is the interface. The live page is on screen in every editor mode. Clicking a line on the page opens its field.
  2. One obvious next step. Each view has at most one accent-filled button. Accent means "do this next" or "this is working" and is never decoration.
  3. Nothing is lost. Everything autosaves and can be undone. Confirmation dialogs are only for irreversible actions.
  4. Detail on demand. Defaults cover most people; advanced controls sit one disclosure deeper.
  5. AI proposes, you decide. The assistant never writes directly; every change is a reviewable proposal.

Resume templates keep their Pokémon names (Azurill, Onyx, Glalie…), their own fonts and their own colors. None of the rules below apply inside a template.

Tokens

Tokens are CSS custom properties in packages/ui/src/styles/globals.css, light on :root and dark on .dark. The source of truth is oklch; the hex values above are sRGB approximations. Tailwind exposes each one under the same name: bg-bg, bg-surface, bg-raised, bg-sunken, border-line, border-line-2, text-ink, text-ink-2, text-ink-3, bg-accent, text-on-accent, bg-accent-soft, text-accent-text, bg-danger, bg-danger-soft, text-danger-text, bg-warn, bg-warn-soft, text-warn-text, bg-info-soft, text-info-text, bg-hover, bg-press, bg-scrim, bg-paper and bg-stage-*.

  • Surfaces: bg is the app desk, surface holds panels and cards, raised holds menus, dialogs and inputs, sunken is for wells, tracks and the page canvas.
  • Text: ink for primary text, ink-2 for secondary text, ink-3 for meta and placeholders. ink-3 is the lightest color allowed for text (about 4.9:1).
  • Signals: danger for errors and irreversible actions, warn for check issues and things to review, info for neutral guidance and the assistant's questions. Success uses accent-soft.
  • Overlays: hover and press are translucent, so they work on any surface.
  • Paper: --paper is white in both themes. Pages never invert.
  • Stages: application stage colors share lightness and chroma. They appear only as 8px dots or 6px stepper bars, always next to the stage name.

The previous shadcn-style names (background, foreground, primary, muted, border, input, ring, destructive, card, popover, sidebar-*) still resolve to these tokens so screens that haven't been rebuilt stay legible. Don't use them in new code; they're removed once every screen has moved.

Typography

  • Newsreader (display serif, optical sizes 6–72) is only for page titles, dialog and sheet titles, empty-state headlines and large stat numerals. Use font-display.
  • Hanken Grotesk handles everything functional. It's the default font-sans.
  • JetBrains Mono is for shortcuts, URLs and slugs, file names, counts and section eyebrows. Use font-mono.
  • Field labels are 12px, medium weight, ink-2, above the control with a 5–6px gap. Uppercase group eyebrows are 12px semibold ink-3 with 0.02em tracking.
  • Inputs render at 16px on touch devices so iOS doesn't zoom.
  • All three fonts are self-hosted through @fontsource-variable.

Iconography

App icons are Material Symbols Rounded at weight 300, rendered by Icon from @reactive-resume/ui/components/icon. The font is a self-hosted subset that contains only the glyphs listed in packages/ui/src/icons/names.ts:

  1. Add the name to that list (TypeScript then accepts it in <Icon name="…" />).
  2. Run pnpm icons:build. The script checks every name against the published codepoints, downloads the subset and updates the manifest. A unit test fails if the manifest and the list disagree.

Rules:

  • 20px on desktop, 24px on touch. Outline by default; filled only for the selected navigation item.
  • Icons always sit beside a text label, except back, close, more, undo/redo, history, assistant and zoom. Those use IconButton, which requires a label and shows it in a tooltip with the shortcut.
  • Icon is aria-hidden and translate="no", so the ligature text never becomes an accessible name.
  • Directional icons (arrows, chevrons, undo, redo) mirror in right-to-left layouts automatically.
  • Icons inside resumes are a separate system: Phosphor, because resume data stores Phosphor names and the PDF renderer draws them.

Space, shape and elevation

  • Spacing follows a 4pt scale: 4, 8, 12, 16, 24, 32, 48, 64. Cards use 16px padding, panels 16–24px, the mobile margin is 16px and the desktop page margin 32–40px.
  • Radius: rounded-sm 6px for chips and small buttons, rounded-md 8px for controls and inputs, rounded-lg 10px for list items, rounded-xl 12px for cards and menus, rounded-2xl 16px for dialogs, rounded-3xl 18px for mobile sheets, rounded-full for pills.
  • Elevation: shadow-e1 for cards, shadow-e2 for menus, popovers and hover-lifted cards, shadow-e3 for dialogs, sheets and toasts, shadow-page for the resume page on the canvas.
  • Control heights: 28px small, 36px default, 44px touch. Icon buttons are 32–36px on desktop and 44px on touch.
  • Layout constants are CSS variables: --editor-bar 56px, --editor-panel 400px, --app-sidebar 240px, --sheet-share 440px, --sheet-detail 480px, --assistant 400px.

Motion

Token Duration Use
duration-quick 120ms hover, press, toggle, checkbox, focus
duration-standard 200ms menus, popovers, expand and collapse, mode switch, dialogs
duration-emphasized 320ms side and bottom sheets, toasts, the assistant column
  • Everything that enters uses ease-enter (cubic-bezier(0.2, 0.8, 0.2, 1)). Exits run at 70% of the duration.
  • Motion explains where something went. Nothing loops, bounces or plays on load; loading placeholders stay still. Reflowing the page after an edit is never animated.
  • With prefers-reduced-motion, the duration tokens become 1ms and every CSS transition collapses; spinners keep turning because they're status.
  • Motion (motion/react) animations run under MotionConfig reducedMotion="user"; mirror the tokens in apps/web/src/libs/motion.ts when one needs them.

Components

Generic primitives live in packages/ui/src/components and wrap Base UI (and cmdk for the command bar). Feature-specific UI lives with its feature in apps/web.

  • Buttons: primary (accent fill, the one filled button per view), secondary (bordered surface), ghost, danger, link. Sizes sm 28, default 36, lg 44 (touch), plus icon sizes. loading shows a spinner, sets aria-busy and blocks activation; pair it with a present-participle label ("Preparing…").
  • Inputs: 36px (44px on touch), raised background, line-2 border. Focus is an accent border plus a 3px accent-soft ring. Errors appear after the first blur, in danger-text, with an icon and words that say how to fix it.
  • Switches: prefer SwitchRow, where the whole row is the switch. Checkboxes are 18px with a 5px radius; radios are 18px with an 8px accent dot.
  • Segmented controls: SegmentedControl for 2–4 options (a radio group); Tabs with the default variant when segments switch panels, Tabs variant="line" for underline tabs.
  • Menus (dropdown, context, combobox lists): 220px minimum width, 12px radius, 36px items, destructive items last after a separator.
  • Layers, lightest to heaviest: menu, popover, sheet, dialog. Sheets are for tasks beside the page and become bottom sheets on mobile. Dialogs are for decisions; destructive confirmations use AlertDialog and the cancel label says what is kept.
  • Toasts: one at a time, bottom center, ink on the desk color, 6 seconds, with an optional underlined Undo action.
  • Alerts: info, success, warn and error; only errors are announced (role="alert").
  • Empty states: a Newsreader 22px headline, a 14px body up to 300px wide, then a primary and a secondary action.

Accessibility

WCAG 2.2 AA is the floor.

  • Every interactive element shows a 2px accent focus ring with a 2px gap on :focus-visible. Never remove it; inputs replace it with their accent border and soft ring.
  • Pointer targets are at least 24px and touch targets at least 44px. Every drag has a keyboard and a menu alternative.
  • Color is never the only signal: stages, issues and states always pair color with text and an icon.
  • Sheets and dialogs trap focus; Esc closes the top layer and returns focus to its trigger. Save state and toasts announce through a polite live region.

Themes

The theme cookie holds light, dark or system (the default); system follows prefers-color-scheme live. ThemeProvider owns the .dark class on <html>, and an inline script in index.html sets it before first paint. Resumes and letters always render on white paper, whatever the theme.

Internationalization

  • Every user-facing string goes through Lingui (t, msg, <Trans>); catalogs are PO files in apps/web/locales. Primitives in packages/ui can't use Lingui, so they take labels as props (for example closeLabel).
  • 55 interface languages, including right-to-left ones. <html dir> follows the locale and DirectionProvider passes it to Base UI.
  • Use logical properties (ps, pe, ms, me, inset-s, inset-e) instead of physical ones.
  • Translations run 30–50% longer than English; avoid fixed widths on text.

Do and don't

  • Do keep one accent-filled button per view, and keep accent for next actions and working states.
  • Do use ink-3 as the lightest text color, and pair every color signal with text.
  • Do build states in full: empty, loading (placeholders at their real size), error (why and what to do) and success.
  • Don't hard-code colors, including Tailwind palette classes such as amber-600; use the semantic tokens.
  • Don't use Newsreader for anything smaller than a sheet title.
  • Don't ask for confirmation for something that can be undone; use an undo toast instead.
  • Don't skip data-slot on primitives; tests and styles rely on it.