Sarva Foam ERP — design system
The working system for 185 screens across 13 team workspaces. Every value on this page is a token; nothing downstream hardcodes one. Contrast ratios are measured, not estimated.
01 Brand and the anti-reflex decision
The scene that decided it
06:40 at the Hosur unit. The night shift's rebonded blocks are coming out of the steam curing chambers and the air above them is still moving. High-bay LED is fighting low orange light through the roof vents. A planner in a cotton shirt is at a wall-mounted terminal, deciding which of four units takes the 32-tonne Sleepwell order before the 08:00 handover.
Glare and daylight force a high-contrast light interface. A dark console would be unreadable on that wall at that hour. Steam, wet steel and cold water force the hue.
What we rejected
Two registers, deliberately different
| Brand register | Product register | |
|---|---|---|
| Where | Landing page, sign-in, printed documents | 180 working screens |
| Voice | Committed, photographic, confident | Restrained, dense, quiet |
| Sarva red | Present as identity | Only ever means rejected / overdue / failed |
| Variance / motion / density | 6 / 4 / 4 | 2 / 2 / 7 |
02 Colour strategy: restrained
Tinted neutrals plus a petrol accent on under 10% of the surface. Neutrals are tinted toward the brand's own hue (196) at 0.003–0.010 chroma — never toward a generic “warm” or “cool”, and never into the cream/sand band. Ratios below are measured with scripts/contrast.py.
03 Status vocabulary
Square tags, not pills: they replace the rubber stamps on a paper batch record, which is literally what this system is displacing. Colour is never the only signal — every tag carries a word, and the ones that gate a handoff carry an icon too.
04 Typography
Display — Barlow Condensed
A condensed grotesk with a signage lineage. It is the voice of the weighbridge slip and the packing list, and practically it fits long field labels into narrow ledger columns without truncating.
Net after deduction
Body — Atkinson Hyperlegible Next
Chosen from the scene rather than from taste. Operators read twelve-character batch codes off a wall terminal under glare, and this face was designed to disambiguate I l 1 and 0 O. A misread lot number in a traceability system is not a cosmetic failure.
GRN-BPL-26-01178
SO-2611 / D110 / 32.400 MT
05 Space, shape and elevation
Form archetype: Ledger. Ruled, tabular, records, no depth. Every value here is a token because the six form decisions are what make one product look different from another — see docs/DESIGN.md §6.
Spacing scale
Shape — three radii, locked
Elevation — hairline only
Panels are bounded records, never floating cards. Shadow is reserved for the three things that genuinely overlay.
Density
| Token | Value | Where |
|---|---|---|
| --h-ctl | 36px | Buttons, inputs, selects |
| --h-ctl-sm | 28px | Toolbar, segmented, chips |
| --h-row | 34px | Table rows |
| --h-ctl-lg | 44px | Shop floor, via .t-lg |
| --bw / --bw-strong | 1px / 1.5px | Hairline; 1.5px under a header only |
06 Controls
Buttons — every variant, size and state
Inputs — default, filled, error, disabled
07 Surfaces and data
The table is the centrepiece of this system, so it is the component that gets the most care: a ruled grid with a sticky head, a sortable column, selection, a bulk bar, a totals foot and right-aligned tabular figures. A ledger always foots.
Order register
| Order | Customer | Grade | Qty (MT) | Value | Promise | Status | Gate | |
|---|---|---|---|---|---|---|---|---|
| 4 open orders | 121.900 | ₹1,42,68,300 | ||||||
Tabs
The active tab is filled, not underlined — on a dense
screen a 2px rule does not answer “where am I”. Counts travel with the label.
Built as Components.tabs(): lazy-mounted panels, the active tab in the URL,
and arrow-key navigation per WAI-ARIA.
- Order
- SO-2611
- Customer
- Sheela Foam Ltd — Sleepwell, Noida
- GSTIN
- 09AABCS1429B1ZP
- Grade
- D110
- Quantity
- 32.400 MT
- Ship from
- Hosur · U2
Menu, drawer and toast
08 Domain components
The parts that exist because this is Sarva Foam and not a generic ERP. This is the section that proves the system was made for this client.
Density grade chip
Density (kg/m³) is the axis this business turns on: it drives the BOM, the yield standard, the price, the QC specification and how much fits on a truck. It appears on every list screen, so it gets one consistent, ordered encoding.
| Block | Grade | Density | Weight |
|---|---|---|---|
| BLK-HSR-2611-0184 | D110 | 110.4 | 418 kg |
| BLK-HSR-2611-0185 | D130 | 129.1 | 497 kg |
| BLK-BPL-2611-0092 | D80 | 79.6 | 302 kg |
Cure countdown — gate G7
A block cannot go to cutting before its cure elapses. The gate is enforced, not described: the button is genuinely disabled.
Weighbridge and yield reconciliation
Not in the SOWSarva buys waste trim by weight at quality it cannot verify at the gate. Net weight is not what you can use. This block turns a weighbridge reading into usable kilograms, and prices the supplier on ₹/usable-kg rather than ₹/kg. It is the largest margin lever in a recycling business and no line in the brief covers it.
Lineage — forward and backward genealogy
Present on every record. Two clicks from a Sleepwell dispatch back to the bale of trims and the supplier who sold it.
Lifecycle — the 13-state sales order
Figure row and load utilisation
Foam is volumetric, not dense. A truck fills by cubic metre long before it reaches its weight limit, so load planning gates on both and names the binding constraint. An ERP that plans foam dispatch on tonnage alone is simply wrong.
09 Feedback and states
The unhappy paths are what convince a buyer that this is a system rather than a screenshot. Every list screen in the build ships all of these.
Empty
No production orders yet
PPC releases orders to the plant once the schedule is confirmed for the shift.
Zero results
Nothing matches “D140”
D140 is not an active grade. The ladder runs D80, D100, D110, D120, D130, D150.
Loading
Permission denied
Production is not your workspace
You are signed in as Sales. Ask a Production team admin for access, or open the linked order instead.
Error
MRP run did not complete
The BOM version for D110 is not approved, so material could not be exploded. Approve it in Masters, then re-run.
Offline — first run
Welcome, Devendra
Your shift starts at 06:00. Three production orders are waiting for Hosur.
10 Iconography
One family, one 1.5px stroke, one 24×24 viewBox, inlined as an SVG sprite in assets/js/icons.js. No emoji, no icon font, no second family.
11 Bans
Written down so the discipline is visible and auditable. These are enforced by scripts/check.py and by review.
Visual
- Emoji as icons; icon fonts; a second icon family
- Gradient text, glassmorphism, glow, purple-blue gradients
- Side-stripe borders — a coloured border-left over 1px as an accent
- Three equal cards as the answer to every section
- A tiny uppercase tracked eyebrow above every marketing section
- Numbered section markers used as scaffolding
- Cream, sand, paper, linen or ivory grounds — or a token named for one
- Shadow doing a border's job; any container radius above 5px
- Dark mode — the brief does not ask, and a half-done one reads as a bug
- Stock handshakes, lightbulbs, people pointing at a laptop
Content and behaviour
- Lorem ipsum, placeholder names, invented-sounding currency
- A TODO, a dead link, a bare href="#"
- alert() — use the toast
- A control that does nothing when clicked
- A list screen without its empty, zero-result and error states
- Pale placeholder text — placeholders take --muted
- Colour as the only signal for a status
- Text overflowing its container at any breakpoint
- Red used for anything in the product except danger
- A hover state that shifts layout