Household Ledger · Design System
The theme sheet
Every visual part the application owns, before any page is built on top of them.
hl-* component vocabulary are carried over token-for-token. A prototype contributes structure — which regions exist, what they are called, which actions they carry. It does not contribute colour, radius or type. Parts marked New did not exist in the original and are built only from the tokens below.Colour
Sixteen tokens, and nothing else. Every colour in the app is one of these; none of them are written as a raw hex anywhere but in :root.
--hl-bg--hl-surface--hl-surface-2--hl-line--hl-ink--hl-muted--hl-dim--hl-accent--hl-accent-soft--hl-gold--hl-gold-soft--hl-dark--hl-good--hl-warn--hl-bad--blue--blue-softType
Two faces. The system sans carries everything that has to be read quickly or read a lot; the serif is reserved for page and area headlines, which is what gives the app its ledger-book feel without costing legibility where text gets dense.
The household’s money at the kitchen table. Warm paper tones, high legibility, generous touch targets — sized for a thumb first and a mouse second.
88,310.55
−417.98
Sans: ui-sans-serif / system-ui / Segoe UI. Serif: Georgia. No web fonts are loaded — the app renders instantly and identically offline.
Buttons
Minimum 44px tall — thumb-sized. One primary per screen region; if two things are green, neither is the primary.
Primary = ledger green, the one action the page is for. Ghost = everything else. Gold = money movement specifically. Danger New is outlined, not filled — a solid red would outshout the primary on every screen it appears on.
40% opacity and a not-allowed cursor. The label never changes.
For secondary actions inside a card whose primary already meets the 44px floor. Keeping all five at 44px is what made the original’s action band as tall as the record it belonged to.
Tab to any control on this page. The ring is gold, deliberately not green — the green already means “active”, and a focus ring the same colour as the active state is a focus ring nobody can find.
Dropdowns
Two different controls that both get called a dropdown. Pick by the job: choosing a value from a list is a select; running a command is a menu.
A real native <select>, restyled. On a phone that means the OS wheel picker, which is faster and more accessible than any custom list.
Press it. Escape closes and returns focus, arrows move between items, Tab is trapped while it is open.
The menu is a small card — same radius, same border, same surface. It never looks like a browser widget dropped onto the page.
Fields
Fields are soft paper wells that turn white on focus, so the field you are typing in is the brightest thing on the form.
Status tags
Compact, uppercase, and the only way status is expressed. A status written as coloured body text is a status that cannot be scanned.
cls carries a classification (what a record is). rolecarries a person’s relationship to it. They are separate tokens because a record can be both, and they must never be read as the same kind of fact.
Callouts
For a sentence the household needs to read before acting. Four tones, matching the status colours exactly — a callout never introduces a colour a tag cannot.
Metrics and meters
Numbers first, label above, unit never abbreviated away.
Meter severities are New. Without them a meter stayed green beside a card that had already gone red.
Local tabs
The second of the two navigation levels. These belong to the area you are in; they are never a second global hierarchy.
Cards
Two kinds. A plain card groups content. A record card represents one real thing and may carry a severity edge.
A plain card. Groups content, claims nothing about status.
Nothing remarkable about this record.
Paid 4 days ago
Due in 3 days
Overdue by 6 days
A record with nothing remarkable about it gets no edge. If every card has a stripe, the stripe stops meaning anything.
Table
Desktop density. Scrolls inside its own frame rather than pushing the page sideways.
| Bill | Account | Due | Amount | Status |
|---|---|---|---|---|
| Water · City Utilities | Joint checking | Aug 12 | 86.40 | Paid |
| Electric · Grid Co | Joint checking | Aug 26 | 214.09 | Due |
| Internet · Fiber One | Household card | Aug 17 | 99.00 | Overdue |
New — the original had no table part. Money columns are tabular-numeral and right-aligned; a column of figures that does not line up is a column nobody reads.
Page states
Five answers to “why is there nothing here”, sharing one shape so they never look like they came from different applications. The authority contract requires the last four on every slot.
Cash Flow is coming soon
The shell around this page is finished — navigation, theme and layout are locked. The page itself is next: its prototype gets translated into React, then connected to data.
Cash Flow is not connected yet
The shell around this page is real — navigation, theme and layout are final. The page itself is waiting on its prototype translation and its data source. Nothing is broken.
No bills yet
When a bill is added it will appear here, sorted by what is due first.
Bills did not load
The connection timed out. Nothing was changed, and trying again is safe.
Neither Coming soon nor Not connected is an error. Nothing is broken — one page has not been built, the other has been built and has no data source. Dressing an on-schedule build stage as a failure trains the household to ignore the words on a real one.
What changed from the original
Everything carried over is unchanged. These are the additions, and why each one exists.
| Added | Why it was needed |
|---|---|
hl-select | The original styled no native select; every dropdown was bespoke. |
hl-menu | The one menu it had was hand-rolled inside the shell, not reusable. |
hl-btn-danger | --hl-bad existed as a status colour but never as a button. |
hl-btn-sm | The 38px secondary size existed inline in one card, unnamed. |
hl-bar-warn / bad | Meters stayed green beside cards that had gone red. |
hl-state | Empty, error and not-connected had no shared shape. |
hl-skeleton | There was no loading treatment at all. |
hl-table | No table part existed. |
hl-switch | No toggle existed. |
hl-page / section | Page furniture was grown per-page and would drift across ten areas. |
--hl-dark | The dark global bar comes from the accepted navigation prototype. |
--hl-display | The serif headline face, also from that prototype. |
Twelve additions, zero new colours. Every one is composed from the sixteen tokens at the top of this page.