Skip to content
BAKERYfor dispensaries
Bakery design system · v2

Build anything Bakery, consistently.

Tokens, components and rules shared by the website, the partner portal and the three Android apps. Dark is the default theme; light is fully supported. Everything here renders from the real components, so it never drifts.

tokens.jsonRepo docs: web/DESIGN-SYSTEM.md

Principles

Dark and product-first

Charcoal surfaces, one bright green for action, generous rounded cards.

One action per screen

A single glowing primary button. Everything else is secondary or a text link.

Plain words first

Say what happens (“Accepted”), then the bakery line (“Proofing”) as flavour, never instead.

Compliance is design

What the public can see, and when, is part of every layout decision. See the rules below.

Colour

Use semantic tokens, never raw hex. Web: CSS variable or Tailwind class (bg-surface, text-leaf-ink). Android: Bakery.colors.<name>. Click a value to copy it.

flour
Page background
Android: flour
surface
Cards, sheets, inputs' container
Android: surface
crumb
Raised elements, chips, input fill
Android: crumb
line
Dividers and borders
Android: line
ink
Primary text and icons
Android: ink
ink-muted
Secondary text
Android: inkMuted
leaf
Brand and action green
Android: leaf
leaf-hi
Top of the action gradient
Android: leafHi
leaf-lo
Bottom of the action gradient
Android: leafLo
on-leaf
Text on green (contrast > 5:1)
Android: onLeaf
leaf-soft
Tinted green backgrounds
Android: leafSoft
leaf-ink
Green text on dark/light backgrounds
Android: leafInk
crust
Warm accent: timers, low stock
Android: crust
ember
Errors and destructive actions
Android: ember
ember-soft
Error backgrounds
Android: emberSoft
ember-ink
Error text
Android: emberInk
oven
Inverse surface (white in dark mode)
Android: oven
on-oven
Text on inverse surface
Android: onOven
sidebar
Portal side menu (web only)
Android: —
Action gradient
bg-linear-to-b from-leaf-hi to-leaf-lo · text-on-leaf · Android: colors.actionBrush

Typography

Urbanist, one family for everything (400, 500, 600, 700, 800). Self-hosted: @fontsource/urbanist (self-hosted, OFL). Headings use the .heading utility (800, tight tracking).

display
72px · 800 · -0.035em
Android: displayLarge (56sp)
Hero headline (web)
headline-xl
48px · 800 · -0.035em
Android: displaySmall (40sp)
Section titles
headline
34px · 800 · -0.035em
Android: headlineMedium (34sp)
Page titles
title
22px · 800 · -0.02em
Android: titleLarge (22sp)
Card titles
body-lg
18px · 500 · 0
Android: bodyLarge (17sp)
Lead paragraphs
body
15px · 500 · 0
Android: bodyMedium (15sp)
Body text
label
13px · 700 · 0
Android: labelMedium (13sp)
Labels, captions
eyebrow
12px · 800 · 0.16em
Android: SectionLabel
Uppercase section labels
button
15px · 800 · 0.09em
Android: ButtonLabel
Uppercase primary buttons

Space, radius, elevation

A 4px grid. Cards 24px radius, buttons 16px, chips 12px. Shadows are rare: the glow belongs to the primary button only.

4
8
12
16
20
24
32
40
64
96
chip · 12px
shapes.small
button · 16px
RoundedCornerShape(16.dp)
card · 24px
shapes.medium (22dp)
hero · 32px
shapes.extraLarge (30dp)
pill · 999px
CircleShape
  • glow: 0 10px 26px var(--leaf-glow) · Primary button only
  • float: 0 30px 80px rgb(0 0 0 / 0.45) · Phone mockups, floating sheets

Buttons

Import { btn } from @/components/ui and apply to <button> or <Link>. Min height 48px. Android: PrimaryButton, SecondaryButton, InverseButton.

<Link href="/apply" className={btn.primary + " no-underline"}>Apply</Link>

Forms

Label above, filled field (bg-crumb), green focus border, error text below in ember-ink with aria-invalid and aria-describedby. Checkboxes use accent-[var(--leaf)].

Enter a South African mobile number, e.g. 082 123 4567.

Tags and chips

Neutral35–50 minClosedSelected chipChipSativaIndicaHybridCBD4.8(312)

Strain tags and ratings are for the partner portal and the apps only, never the public website.

Cards

Surface background, 24px radius, a 1px line border when cards sit on surface. Hover lifts the border to leaf/50.

FeatureCard

Icon tile, title, one or two sentences. Used on the marketing site.

StatTile
23
New order · #BK-20431
4:12
Lerato K. · Delivery · 2 items
Decline
Accept
  • CheckList: green tick bullets
  • Use for benefits and requirements

Icons

24×24, 2px round stroke, currentColor. Import from @/components/icons. Android has the same shapes in BakeryIcons.

ArrowRight
Back
Bag
Bell
Box
Chart
Chat
Check
Clock
Download
Lock
Mail
Menu
Palette
Phone
Pin
Plus
Scooter
Search
Shield
Sparkle
Store
Sync
Users
Wallet
WhatsApp

Motion

Quick and springy, never decorative for long. Respect prefers-reduced-motion on web.

  • fast · 160 ms · Colour and opacity changes
  • medium · 280 ms · Page and sheet transitions
  • slow · 480 ms · Entrances (fade + 18px rise)
  • stagger · 45 ms · Delay between list items (first 10 only)

Android equivalents live in core-ui Motion.kt: pressScale, enterUp, popIn, bounceOnChange, shake, SuccessCheck, ConfettiBurst.

Compliance rules

These are hard rules. Breaking one is a bug. `npm run check:compliance` enforces the public-site rules.

Public website = dispensaries only

The website markets Bakery to dispensaries, hosts partner login, legal pages and app downloads. Nothing else.

No products, menus or prices in public

No product listings, strain cards, menus, prices or ordering anywhere a visitor can reach without signing in. Product art may appear only as abstract brand imagery.

Revenue figures are labelled

A dispensary's own sales figures (calculator, dashboard mockups) are allowed inside an element with data-compliance="revenue-estimate".

18+ before the patient app

Every patient-app download or call to action sits behind an explicit 18+ confirmation.

No health or effect claims

Don't promise effects or medical benefits. Speak to businesses about operations, not to consumers about use.

The API is private

Every /api/v1 route except /health requires a signed-in app user. Menus and prices are served only to verified patients in the app.

Portals are gated

/partners needs a partner session; /admin needs an admin session. Both are noindex.

POPIA

Collect the minimum, say why at the point of collection, link the Privacy Notice, never log ID numbers or tokens.

Accessibility

  • Text contrast ≥ 4.5:1; dark text (on-leaf) on green buttons
  • Touch targets ≥ 44px; visible :focus-visible outline in leaf
  • Every input has a <label>; errors use aria-invalid + aria-describedby
  • Decorative art has empty alt and aria-hidden; icons beside text are aria-hidden
  • Layouts work from 320px wide with no horizontal scroll

For agents

Start here when you extend Bakery.

  1. Read web/DESIGN-SYSTEM.md and fetch /design-system/tokens.json.
  2. Reuse components from web/src/components (ui, site, icons, logo) before writing new ones. New shared pieces go there and get a section on this page.
  3. Use semantic tokens only. If you need a new token, add it to globals.css, design-tokens.ts and Android Color.kt together; npm run check:tokens must pass.
  4. Keep public copy in src/lib/site.ts; follow its compliance header.
  5. Before you finish: npm run typecheck && npm run build && npm run check:compliance.