DESIGN SYSTEM

WineBox Design System

A living reference for the visual and UX language of WineBox. Everything here uses the real style.css — if something looks wrong on this page, it's wrong in the app.

Colour

Brand palette and semantic aliases. Always use semantic aliases (--primary-color, --border-color) in component CSS — keep brand names reserved for the palette itself.

Brand palette

Burgundy
#5C0A2D · --burgundy
Burgundy Light
#8B1A4A · --burgundy-light
Rose
#B82860 · --bottle
Gold
#C49A3C · --gold
Gold Light
#F0D78C · --gold-light
Case Wood
#B8956A · --case-wood
Cream
#FAF7F2 · --cream
Cream Dark
#F0EBE3 · --cream-dark
Dark BG
#1A0A10 · --dark-bg

Semantic aliases

Primary
--primary-color
Primary Light
--primary-light
Primary Dark
--primary-dark
Secondary
--secondary-color
Background
--background-color
Card
--card-background
Border
--border-color
Text
--text-color

Gradients

Nav / CTA
burgundy-light → burgundy
Hero
cream → cream-dark

Typography

Headings use Playfair Display (serif). Body and UI use DM Sans with a system fallback stack. Serif never appears in buttons, labels, or inputs.

Heading 1 · Playfair Display

font-family: 'Playfair Display'; · weight: 700 · from style.css h1 rules

Heading 2 · 1.75rem

Used as page title

Heading 3 · section label

Used inside cards and modals

Body paragraph · 1rem · DM Sans. A cellar is a curated collection — the copy should read like it was written by someone who cares about wine, not someone documenting a database.

font-family: 'DM Sans', system stack · weight: 400

Secondary text · 0.875rem · muted. Used for helper copy, timestamps, and supporting details.

color: var(--text-muted)
Stat label 0.75rem uppercase — deliberate accent on stat cards only

Spacing

Based on a 0.25rem (4px) unit. Do not invent one-off values.

0.25rem
0.5rem
0.75rem
1rem
1.25rem
1.5rem
2rem

Border radius

Two tokens cover nearly every case: --radius for inputs & buttons, --radius-lg for cards & modals.

2px · badge
4px · logo case
6px · --radius-sm
8px · --radius
12px · --radius-lg
pill

Elevation

Only two levels: default and hovered/raised. Plus a focus ring on inputs.

--shadow
--shadow-md
--shadow-hover
focus ring

Buttons

Every button has the .btn base class plus one variant. Labels start with a verb and use sentence case.

Variants

Sizes

States

Link styled as button

Forms

Every input has a visible label. Focus state is burgundy border + soft glow — never suppress it.

Image upload

Preview will appear here

Cards

White fill, 12px corners, soft shadow. Use .stat-card for numeric tiles — always pair a .stat-value with a .stat-label so no number appears naked.

245
Bottles in cellar
3.8
Avg rating (245 wines)
14.5%
Average ABV
£3,420
Total cellar value

Modals

Background scrim dims the page. Three widths: small (≤400px), default (≤800px), large (≤900px). Buttons stack full-width inside small modals.

Status colours

There is no dedicated alert component yet. When you need to indicate status, use these semantic tokens.

Success · saved
Warning · verify vintage
Error · import failed

Alerts

Inline status messages. Paired with a semantic colour. Use for non-blocking feedback inside a page flow. Title is optional; body is always required.

Bottle added Château Margaux 2015 is now in your cellar.
Tip · drag a photo onto the scan area to skip the file picker.

Toasts

Transient feedback floating outside the page flow. Use after a background action finishes — not for form-level errors (those are alerts, §2.6). Errors are sticky by default; success/warning/info auto-dismiss after 4 seconds.

Trigger a toast

Click any button below — the toast appears top-right (or bottom on mobile). Toasts stack and dismiss on click of the × or after their timeout.

API

WineBox.toast.success("Bottle added");
WineBox.toast.error("Couldn't import - three rows missing a wine name.");
WineBox.toast.warning("Vintage looks unusual", { title: "Double-check" });
WineBox.toast.info("Tip - drag a photo onto the scan area");

// Full form
WineBox.toast.show("Custom message", {
  variant: "success",   // success | warning | error | info
  title: "Optional",
  duration: 6000,       // ms; 0 = sticky (errors default to sticky)
  dismissible: true     // show X button (default true)
});

Rules

  • Errors are sticky by default — never auto-dismiss something the user must act on.
  • One short sentence. If you need more, link to a page or open a modal instead.
  • Don't toast confirmation for things the user already sees in the UI.
  • Don't stack more than three at once. If you would, you're using toasts for the wrong thing.

Empty states

Every list or dashboard panel has one. Title is a short noun phrase; description explains what belongs here; up to two actions fill the gap.

Your cellar is empty
Start by scanning a bottle's label or importing a spreadsheet of what you already own.

Badges

Small pill labels for categorical status. One badge per item — more and the list becomes noisy. Never use a badge as the only signal for a number.

Variants

Unrated Red Drink now Peak soon Past peak Reserve

In context

Château Margaux Red
2015 · Bordeaux · Peak soon
Cloudy Bay White
2022 · Marlborough · Drink now

Icons

All UI icons share a 24-unit grid, stroked paths in currentColor, and one of five fixed sizes. Apply .icon plus an optional size modifier to any inline SVG.

Size scale

.icon-sm · 16px
.icon-md · 20px
.icon-lg · 24px
.icon-xl · 32px · stroke 1.5
.icon-2xl · 48px · stroke 1.5

Inline with text

Plain .icon (no size modifier) inherits font size — drop into buttons, links, list items.

Updated 2 minutes ago Larger heading text — icon scales with em

Stroke-only, current colour

Icons inherit colour from the parent. Set color on the wrapper, never hardcode the stroke.

primary success warning error muted

Authoring rules

  • Always viewBox="0 0 24 24" · always fill="none" with stroked paths.
  • Don't set inline width, height, or stroke-width — the size class does it.
  • Decorative icon next to text → aria-hidden="true".
  • Icon-only button → aria-label on the button.

Voice & tone

The reader is a wine enthusiast, not a developer. Use wine vocabulary, not system vocabulary.

Words to use vs. avoid

Do
  • cellar · bottle · case · label
  • vintage · varietal · tasting note
  • "Record a wine"
  • "You have 42 bottles"
  • "14.5% ABV"
Avoid
  • record · entry · document · batch
  • row · field · object · endpoint
  • "Submit form"
  • "42 records found"
  • "14.5" (naked number)

Button label rules

  • Start with a verb: Save changes, Record wine, Import cellar.
  • Never just OK, Submit, or Click here.
  • Destructive actions are specific: Delete bottle, not Delete.

Full guidelines

See DESIGN-SYSTEM.md §3 in the brand kit for the complete voice & tone reference, including empty states and accessibility baseline.