Design System
A warm dual-canvas design system for a personal web app. The system anchors on a warm sand canvas (light) that flips to a deep warm-charcoal canvas (dark), with one olive-green primary and two warm accents (amber + rust). Brand voltage comes from the olive/sand pairing combined with tactile retro hardware — primary controls are 36px 3D keys whose ledge is contained inside their measured height. Type voice is JetBrains Mono for display headings, titles and code, paired with variable Noto Sans for body copy, labels and controls. ASCII-art panels and a terminal motif carry the technical character.
This page is generated from DESIGN.md (tokens) and the @workspace/ui barrel (components) — it stays in sync automatically.
A warm sand canvas in light flips to deep warm-charcoal in dark. Toggle the theme in the header — every token, swatch, and component below re-renders live.
Every primary control is a key resting on a colored ledge: it drops halfway on hover and levels flat on press. Tactile retro hardware, in pure CSS.
JetBrains Mono gives display headings, titles and code their technical character; Noto Sans keeps body copy, labels and controls clear and comfortable.
A terminal motif and animated ASCII panels carry the technical character across the surface.
Full written spec, do's and don'ts, and dark-mode mapping live in /DESIGN.md.
Basketball's TipTap editor keeps its canvas clear: type / for insertions, @ for a player, select text for inline formatting, and focus a table cell for row and column actions. Uploads become inline image previews or file chips; YouTube and HoopsGeek links become responsive embeds, while the stored value remains portable Markdown.
Mention treatment: @Šimon Svoboda
14 semantic, role-named colors. Swatches use the live CSS variable, so they track the active theme; the hex shown is the light-mode value.
The ledge recipes that give controls their depth. Seen live on the 3D keys and elevated cards below.
| ledge-rest | 0 4px 0 <ledge> |
| ledge-hover | 0 2px 0 <ledge> |
| ledge-pressed | 0 0 0 <ledge> |
| card-ledge | 0 4px 0 #dcd6c7 |
| shadow-amb-light | rgba(40,36,22,0.18) |
| shadow-amb-dark | rgba(0,0,0,0.4) |
| press-transition | transform .08s ease, box-shadow .08s ease |
| hover-drop | translateY(2px) |
| press-level | translateY(4px) |
| theme-transition | background .25s ease, color .25s ease |
| cursor-blink | blink 1.1s steps(1) infinite |
| progress-fill | width .3s ease |
| overlay-fade | opacity .16s ease |
| sheet-slide | transform .2s ease-out |
38 component recipes mapping the primitives above onto concrete parts.
| button-primary | backgroundColor: {colors.primary} · textColor: {colors.on-primary} · typography: {typography.button} · rounded: {rounded.lg} · padding: 0 16px · height: 36px |
| button-primary-pressed | backgroundColor: {colors.primary-active} · textColor: {colors.on-primary} |
| button-accent | backgroundColor: {colors.accent} · textColor: {colors.on-accent} · typography: {typography.button} · rounded: {rounded.lg} · padding: 0 16px · height: 36px |
| button-danger | backgroundColor: {colors.danger} · textColor: {colors.on-danger} · typography: {typography.button} · rounded: {rounded.lg} · padding: 0 16px · height: 36px |
| button-secondary | backgroundColor: {colors.surface} · textColor: {colors.ink} · typography: {typography.button} · rounded: {rounded.lg} · padding: 0 16px · height: 36px |
| button-ghost | backgroundColor: transparent · textColor: {colors.primary} · typography: {typography.button} · rounded: {rounded.lg} · padding: 0 16px · height: 36px |
| button-disabled | backgroundColor: {colors.surface-2} · textColor: {colors.muted-strong} · typography: {typography.button} · rounded: {rounded.lg} · padding: 0 16px |
| switch | backgroundColor: {colors.surface-2} · textColor: {colors.ink} · rounded: {rounded.pill} · width: 56px · height: 36px |
| tabs | backgroundColor: {colors.surface-2} · textColor: {colors.body} · typography: {typography.label} · rounded: {rounded.lg} · height: 36px |
| dropdown | backgroundColor: {colors.surface} · textColor: {colors.body} · typography: {typography.caption} · rounded: {rounded.lg} · padding: 5px |
| select | backgroundColor: {colors.canvas} · textColor: {colors.ink} · typography: {typography.caption} · rounded: {rounded.md} · height: 36px |
| checkbox | backgroundColor: {colors.canvas} · textColor: {colors.ink} · rounded: {rounded.sm} · width: 20px · height: 20px |
| radio | backgroundColor: {colors.canvas} · textColor: {colors.ink} · rounded: {rounded.full} · width: 20px · height: 20px |
| mention-tag | backgroundColor: {colors.accent} · textColor: {colors.ink} · typography: {typography.caption} · rounded: {rounded.pill} · padding: 1px 6px |
| progress-bar | backgroundColor: {colors.surface-2} · textColor: {colors.ink} · typography: {typography.label} · rounded: {rounded.pill} · height: 24px |
| segmented-progress | backgroundColor: {colors.surface-2} · textColor: {colors.ink} · typography: {typography.label} · rounded: {rounded.pill} · height: 20px |
| stepper-field | backgroundColor: {colors.canvas} · textColor: {colors.ink} · typography: {typography.caption} · rounded: {rounded.lg} · height: 36px |
| sheet | backgroundColor: {colors.surface} · textColor: {colors.body} · rounded: {rounded.xl} · padding: 20px |
| dialog | backgroundColor: {colors.surface} · textColor: {colors.body} · rounded: {rounded.xl} · padding: 24px |
| text-input | backgroundColor: {colors.canvas} · textColor: {colors.ink} · typography: {typography.caption} · rounded: {rounded.md} · padding: 0 12px · height: 36px |
| textarea | backgroundColor: {colors.canvas} · textColor: {colors.ink} · typography: {typography.caption} · rounded: {rounded.md} · padding: 11px 14px · height: 112px |
| card | backgroundColor: {colors.surface} · textColor: {colors.body} · rounded: {rounded.xl} · padding: 26px |
| card-elevated | backgroundColor: {colors.surface} · textColor: {colors.body} · rounded: {rounded.xl} · padding: 26px |
| ascii-panel | backgroundColor: {colors.surface} · textColor: {colors.primary} · typography: {typography.code} · rounded: {rounded.xl} · padding: 18px |
| terminal-card | backgroundColor: {colors.surface} · textColor: {colors.body} · rounded: {rounded.xl} |
| terminal-titlebar | backgroundColor: {colors.surface-2} · textColor: {colors.body} · typography: {typography.code} · padding: 11px 16px |
| badge-pill | backgroundColor: {colors.surface-2} · textColor: {colors.body} · typography: {typography.label} · rounded: {rounded.pill} · padding: 5px 11px |
| badge-primary | backgroundColor: {colors.primary} · textColor: {colors.on-primary} · typography: {typography.label} · rounded: {rounded.pill} · padding: 5px 11px |
| badge-accent | backgroundColor: {colors.accent} · textColor: {colors.on-accent} · typography: {typography.label} · rounded: {rounded.pill} · padding: 5px 11px |
| badge-danger | backgroundColor: {colors.danger} · textColor: {colors.on-danger} · typography: {typography.label} · rounded: {rounded.pill} · padding: 5px 11px |
| status-dot | backgroundColor: {colors.primary} · rounded: {rounded.full} · size: 8px |
| top-nav | backgroundColor: {colors.canvas} · textColor: {colors.ink} · typography: {typography.label} · height: 56px |
| product-shell | backgroundColor: {colors.canvas} · textColor: {colors.body} · typography: {typography.body-md} |
| product-nav-link | backgroundColor: {colors.surface} · textColor: {colors.ink} · typography: {typography.button} · rounded: {rounded.lg} · height: 36px |
| breadcrumbs | backgroundColor: {colors.canvas} · textColor: {colors.muted} · typography: {typography.caption} · padding: 0 |
| footer | backgroundColor: {colors.surface} · textColor: {colors.muted} · typography: {typography.caption} · padding: 40px 32px |
| table | backgroundColor: {colors.surface} · textColor: {colors.body} · typography: {typography.body-md} · rounded: {rounded.xl} · padding: 12px 16px |
| table-header | backgroundColor: {colors.surface-2} · textColor: {colors.muted} · typography: {typography.label} · padding: 12px 16px |
39 components from @workspace/ui/olive. This list is driven by the barrel exports; a component can't ship without appearing here.
Component inventory
39/39 documented<Badge />PrimitivesPill-shaped uppercase tag in the UI sans; variants map to semantic tokens.
<Badge>Default</Badge>
<Badge variant="primary">Primary</Badge><Card />PrimitivesRounded, hairline-bordered surface panel; `elevated` adds the 6px ledge.
A resting surface panel.
Raised on the hairline ledge.
<Card>…</Card>
<Card elevated>…</Card><CardTitle />PrimitivesSans uppercase overline for card headers.
Pairs with any Card body.
<CardTitle>Section label</CardTitle><Checkbox />PrimitivesNative checkbox behavior in a compact Olive control with checked, focus, description, and disabled states.
<Checkbox
label="Include archived items"
checked={checked}
onChange={(event) => setChecked(event.target.checked)}
/><Dropdown />PrimitivesShared command and context menu shell with normal-weight labels, optional icons and hints, bounded scrolling, active state, footer, separators, and danger actions.
<Dropdown floating role="listbox">
<Dropdown.List ref={listRef}>
<Dropdown.Item icon={<Search />} active trailing="⌘K">Search</Dropdown.Item>
<Dropdown.Separator />
<Dropdown.Item tone="danger">Delete</Dropdown.Item>
</Dropdown.List>
<Dropdown.Footer>3 commands</Dropdown.Footer>
</Dropdown><Input />PrimitivesSans text input with the system's single focus ring; optional label.
<Input label="Email" placeholder="you@example.com" /><ProgressBar />PrimitivesDeterminate semantic progress bar with primary, accent, or danger fill and an optional centered label.
<ProgressBar value={5} max={8} tone="accent" label="63% (5/8)" /><Radio />PrimitivesNative radio behavior with the same geometry, focus treatment, and ledged selected state as Checkbox.
<Radio name="cadence" label="Weekly" value="weekly" /><SegmentedProgress />PrimitivesTwo-to-four independently valued progress segments that double as navigation for a short ordered workflow.
<SegmentedProgress
activeId="work"
items={[
{ id: "prepare", label: "Prepare", value: 4, max: 4, tone: "accent" },
{ id: "work", label: "Work", value: 3, max: 8, tone: "primary" },
]}
onSelect={setStage}
/><Select />PrimitivesA 36px single-value Select whose popup uses the shared Dropdown surface and item behavior.
<Select
value={status}
onValueChange={setStatus}
options={[{ value: "active", label: "Active" }]}
/><StatusDot />PrimitivesA colored status dot (online/idle/offline) with an optional label.
<StatusDot status="online" label="online" /><StepperField />PrimitivesDirectly editable 36px numeric input with adjacent decrement and increment controls; supports decimal or numeric keyboards.
<StepperField
label="Weight (kg)"
value={weight}
onChange={setWeight}
onDecrease={() => nudge(-0.5)}
onIncrease={() => nudge(0.5)}
/><Switch />PrimitivesWide, Apple-like 36px switch with an Olive active track, sliding thumb, disabled state, and shared focus ring.
<Switch
checked={enabled}
onCheckedChange={setEnabled}
aria-label="Enable feature"
/><Tabs />PrimitivesControlled 36px segmented row with a subtly ledged active tab that slides between selections; supports text-only, icon-only, and icon-plus-text items.
<Tabs
aria-label="Sets"
activeId={activeId}
items={[{ id: "home", label: "Home", icon: <Home /> }, { id: "search", icon: <Search />, ariaLabel: "Search" }]}
onChange={setActiveId}
/><Textarea />PrimitivesMultiline Olive form control with the shared label and focus treatment.
<Textarea label="Description" placeholder="Add a few useful details…" /><AsciiField />Terminal & ASCIIFull-bleed animated ASCII background that fills its parent; honors reduced-motion.
<div className="relative h-40 overflow-hidden">
<AsciiField pattern="rain" />
</div><AsciiPanel />Terminal & ASCIIBordered, elevated ASCII-art panel (wave / grid / noise).
<AsciiPanel pattern="wave" /><BlinkCursor />Terminal & ASCIIThe brand's punctuation — a solid olive block blinking at 1.1s.
booting
booting<BlinkCursor /><TerminalCard />Terminal & ASCIIFaux terminal window — traffic-light titlebar over a mono body.
$ whoami
peter
<TerminalCard title="~/peterhq — zsh">
<p>$ whoami</p>
</TerminalCard><Wordmark />Terminal & ASCIIThe peter/hq wordmark with the slash in olive primary.
<Wordmark /><Reveal />MotionFade + slide a block into view on first paint; `delay` staggers a group.
<Reveal delay={0.15}>
<Card>…</Card>
</Reveal><Typewriter />MotionTypes out text, then an olive accent, followed by a blinking caret.
<Typewriter text="Hello there" accent="…" /><Dialog />LayoutAdaptive modal: a bottom sheet on mobile and centered dialog on larger screens, with the same accessible overlay behavior.
<AnimatePresence>
{open ? <Dialog title="Confirm change" onClose={() => setOpen(false)}>…</Dialog> : null}
</AnimatePresence><OnThisPage />LayoutAuto-generated, scroll-spy table of contents; reads [data-toc] elements from the live DOM — shown live in this page's sidebar.
Live in the sidebar
The numbered index on the left of this page is a live OnThisPage. It discovers every [data-toc] heading at runtime — no second copy to maintain.
<OnThisPage label="On this page" />
// mark headings with: <h2 id="…" data-toc data-level="2"><ProductShell />LayoutAuthenticated product chrome with an inset app-name sidebar, a top-attached backed breadcrumb header with optional global actions, a collapsible desktop rail, and mobile profile dock.
<ProductShell
appName="Workout"
appIcon="🏋️"
navigation={<ProductNavLink active>Training week</ProductNavLink>}
appSwitcher={<ProductNavLink>All apps</ProductNavLink>}
headerActions={<FeedbackButton />}
>
{children}
</ProductShell><Sheet />LayoutAnimated bottom sheet with a standard scrim, flat X close control, focus trap/restoration, Escape and backdrop dismissal, safe-area padding, and body-scroll lock.
<AnimatePresence>
{open ? <Sheet title="Details" onClose={() => setOpen(false)}>…</Sheet> : null}
</AnimatePresence><Callout />ContentHighlighted aside with a colored left rule; tone picks a semantic token.
Note
Primary-toned aside.
Careful
Danger-toned aside.
<Callout tone="primary" title="Note">…</Callout><StatTiles />ContentResponsive KPI tile row; headline value colored by accent.
<StatTiles columns={3} stats={[{ value: "13", label: "Color tokens" }]} /><Table />ContentThe standardized bordered data table used across the site — Notion-style header, hairline rows, an emphasized first column, two-axis constrained scrolling, optional sticky headings, and accessible whole-row activation. Data-driven via head/rows (with optional align, zebra, mono).
| Token | Value | Use |
|---|---|---|
| primary | #66722f | Brand / primary actions |
| accent | #c2962f | Secondary emphasis |
| danger | #b1583a | Destructive actions |
<Table
head={["Token", "Value", "Use"]}
rows={[
["primary", "#66722f", "Brand / primary actions"],
["accent", "#c2962f", "Secondary emphasis"],
]}
/><BarChart />ChartsDependency-free, theme-aware SVG bar chart; recolorable per datum.
<BarChart data={[{ label: "Mon", value: 32 }]} /><BubbleChart />ChartsSVG scatter / bubble chart with linear or log x-scale.
<BubbleChart data={[{ label: "Alpha", x: 2000, y: 3, size: 20 }]} /><DonutChart />ChartsSVG donut with a legend; values sum to a ring.
<DonutChart data={[{ label: "Primary", value: 52 }]} unit="%" /><HBarChart />ChartsHorizontal bar chart (div-based, crisp labels).
<HBarChart data={[{ label: "Research", value: 72 }]} unit="%" /><LineChart />ChartsMulti-series SVG line chart; palette cycles per series.
<LineChart labels={["Jan","Feb"]} series={[{ name: "A", points: [20, 42] }]} />