Scran design system
Premium through restraint. Clean neutral base, black type doing the hierarchy work, one signature colour used sparingly. Glass is a layering tool, never an effect. The system is the chrome; the vendor's brand is the tint.
Colour
Accent discipline: if a screen uses accent in more than ~3 places, it is wrong. Accent means primary CTA, live state, selection. Nothing else.
Light (default)
Dark overrides
Surface theme defaults
Light default
Admin, Platform, Storefront, Customer app, Marketplace, Vendor Hub, Super Vendor, Kiosk, Till, Scanner, Order Manager
Dark default
KDS (kitchen glare), Signage / menu boards, Driver (night driving)
Staff tools (till, KDS, scanner) also apply the reduce-transparency high-contrast variant: solid background, hairline border, no blur.
Type — Hanken Grotesk
One family, six roles. Money is always tabular. Hierarchy comes from weight and size, never colour.
Shape — radius tokens
Four steps: sm 8, md 12, lg 16, pill 999. Clean but not bubbly. Buttons use md, cards lg, badges and status dots pill.
--radius-sm8pxInput corners, small chips--radius-md12pxButtons, form controls (rounded-md)--radius-lg16pxCards, panels, tiles (rounded-lg)--radius-pill999pxBadges, switch track, spinner (rounded-pill)Elevation
Three tiers: page content, card (hairline border, no shadow), floating glass. That is the whole z-axis.
Tier 0 · Page
Content area
No border, no shadow. Canvas background.
Tier 1 · Card
Card / well
Hairline border. No shadow in light theme.
Tier 2 · Glass
Floating glass
Nav bars, sheets, popovers. Blur 24, soft shadow.
.reduce-transparency on the root. Same component code, token-level switch. Doubles as the accessibility mode product-wide.Motion
Two durations, one easing curve. Motion conveys state change, not decoration. No orchestrated page-load sequences on staff tools.
motion.standard150msButtons, badges, tab underlines, hover statesmotion.sheet250msSheet/drawer slide, dialog fade-in--ease-scrancubic-bezier(0.2, 0, 0, 1)Ease-out-expo feel. Used for all transitions.prefers-reduced-motion: typically a crossfade or instant transition instead of a slide. No decorative animation on staff tools (till, KDS, scanner).Components
Every block rendered in light and dark. Source: @getscran/ui-web.
Light
Button — variant + size
Badge
Form controls
Shown on menus and receipts
Price is required
Tabs
Dialog + sheet
Table
| Item | Status | Price |
|---|---|---|
| Classic burger | Live | £6.50 |
| Coke 330ml | Low · 4 | £1.40 |
| Fries | Draft | £2.99 |
Loading states
Empty state
No orders yet
New orders from every channel land here in realtime. Share your shop link to start receiving orders.
Dark
Button — variant + size
Badge
Form controls
Shown on menus and receipts
Price is required
Tabs
Dialog + sheet
Table
| Item | Status | Price |
|---|---|---|
| Classic burger | Live | £6.50 |
| Coke 330ml | Low · 4 | £1.40 |
| Fries | Draft | £2.99 |
Loading states
Empty state
No orders yet
New orders from every channel land here in realtime. Share your shop link to start receiving orders.
Patterns
Composed pieces the products are built from.
StatCard — dashboard metrics
Delta direction is weight + glyph, not colour. Down delta uses danger text. No delta = just value.
OrderCard — order queue
Ageing states: ok (muted), warn (warning colour), late (danger). Channel badge is always outline. Status badge is contextual.
- 2× Classic burger+ cheese
- 1× Fries
- 1× Chicken wrapno mayo
- 3× Waffle stack
TileCard — browse grid
Unified menu/category tile across storefront, customer app, till, kiosk. Pass href for navigation (renders an <a>, crawlable, middle-clickable); pass onClick for app-like surfaces (till). Clients render what /browse returns and never branch on vendor type.
Skeleton loading pattern
Replace content regions with Skeleton while data loads. No full-page spinners. Maintain layout structure so the loaded state does not cause a visual jump.
Teaching empty states
Empty states explain what the section does and give a next action. Never blank. Never just "No items."
No orders yet
New orders from every channel land here in realtime.
No items in this category
Add items to start building this section of your menu.
Price component
Always integer pence. Always tabular numerals. Always SemiBold. Use Price pence={number} or formatPence() for text contexts. Never format money ad-hoc with string concatenation.