Ledger
Every edge is a hairline, every corner is square, every headline is a serif. Colour only means status.
- Light by default
- 35 colours · 14 type steps
- Inspired by midday.ai
The same generated app in every design. Scroll inside the window to see the whole page.
What it is
Ledger draws the interface like a ruled book. One warm 1px hairline marks every edge, corners are square, and only things that float, like menus and dialogs, cast a shadow. Headlines are set large in Hedvig Letters Serif over a single-weight Hedvig Letters Sans, with small labels in tracked capitals and figures in IBM Plex Mono. Actions are flat ink blocks, and the only colour on screen is a status: a green dot, an amber chip, a red label. It opens in light, white with warm grey lines, and its dark palette is the same book on near-black.
Maintained by @agentic-studio in the open registry.
Best for
Money and operations products where tables and figures carry the screen: invoicing, bookkeeping, analytics, admin consoles and internal tools. Also calm, text-first marketing sites that want to look exact rather than loud.
Trade-offs
- Flat and square on purpose. With no radius and no shadow on the page, hierarchy comes only from space, type and rules, so a crowded screen looks crowded at once.
- One real weight. Hedvig Letters Sans ships only 400, so emphasis comes from size, colour and capitals. font-medium and font-semibold render as regular, and only strong gets a synthetic bold.
- Greyscale charts. Series differ by lightness alone. The first three are spread wide, but past three series a chart needs direct labels, and a forecast line needs a dash.
Tokens
Generated from this design’s design.yaml, and shipped as CSS variables plus a Tailwind theme. Templates may use these names and nothing else.
Colours
Every colour token in design.yaml, grouped the way the system documents them. These are the light palette's values. The dark palette uses the same names with its own values, and tokens.css ships both.
Brand and accent
accent
#121212
accent-1
#23211e
accent-2
#7d7c78
accent-3
#474642
accent-4
#959490
accent-5
#65635f
accent-6
#b1afab
accent-hover
#2f2f32
accent-soft
#ebeae7
primary
#18181b
Surface
canvas
#ffffff
hairline
#dbdad7
surface-card
#f3f2ef
surface-dark
#121212
surface-dark-elevated
#1c1c1c
surface-soft
#f7f6f4
surface-solid
#f7f6f4
surface-strong
#e6e4e0
Text
body
#4a4a4a
body-strong
#242424
ink
#121212
muted
#616161
muted-soft
#8a8a8a
on-accent
#fafafa
on-dark
#fafafa
on-primary
#fafafa
Semantic
error
#a43b36
success
#006e30
warning
#785a00
Other
danger
#a43b36
danger-soft
#f7eceb
error-soft
#f7eceb
primary-hover
#2f2f32
success-soft
#eaf2eb
warning-soft
#f3efe6
Type scale
Each token rendered at its real size, weight, line height and tracking. Specimens fall back to this site's typeface when the design's own face is not installed locally, so read the numbers, not the letterforms.
Ship faster
- Token
- display-xl
- Values
- 4.5rem · 72px · 400 · lh 1 · ls -0.025em
Ship faster
- Token
- display-lg
- Values
- 3.5rem · 56px · 400 · lh 1.04 · ls -0.025em
Ship faster
- Token
- display-md
- Values
- 2.5rem · 40px · 400 · lh 1.1 · ls -0.02em
Ship faster
- Token
- display-sm
- Values
- 2rem · 32px · 400 · lh 1.15 · ls -0.015em
Ship faster
- Token
- title-lg
- Values
- 1.5rem · 24px · 400 · lh 1.33 · ls 0
Ship faster
- Token
- body-lg
- Values
- 1.125rem · 18px · 400 · lh 1.56 · ls 0
Ship faster
- Token
- title-md
- Values
- 1.125rem · 18px · 400 · lh 1.45 · ls 0
Ship faster
- Token
- title-sm
- Values
- 1rem · 16px · 400 · lh 1.5 · ls 0
Ship faster
- Token
- body-md
- Values
- 0.9375rem · 15px · 400 · lh 1.6 · ls 0
Ship faster
- Token
- body-sm
- Values
- 0.875rem · 14px · 400 · lh 1.5 · ls 0
Ship faster
- Token
- button
- Values
- 0.875rem · 14px · 400 · lh 1 · ls 0
Ship faster
- Token
- nav-link
- Values
- 0.875rem · 14px · 400 · lh 1.4 · ls 0
Ship faster
- Token
- caption
- Values
- 0.75rem · 12px · 400 · lh 1.5 · ls 0
SHIP FASTER
- Token
- caption-uppercase
- Values
- 0.6875rem · 11px · 400 · lh 1.5 · ls 0.08em
Typefaces
The families the generated Tailwind theme points at. A licensed face is named here even when the substitute below is what actually renders.
- DisplayHedvig Letters Serif, ui-serif, Georgia, "Times New Roman", serif
- BodyHedvig Letters Sans, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif
- MonoIBM Plex Mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace
Spacing scale
Drawn at true size. These are the only gaps and paddings a generated repo is allowed to use.
- xxs0.25rem · 4px
- xs0.5rem · 8px
- sm0.75rem · 12px
- md1rem · 16px
- lg1.5rem · 24px
- xl2rem · 32px
- xxl3rem · 48px
- section6rem · 96px
Radius scale
Sample shapes at true corner radius. Buttons and inputs sit at the small end of this scale, feature cards at the large end.
- lg0 · 0px
- md0 · 0px
- pill0 · 0px
- sm0 · 0px
- xl0 · 0px
- xs0 · 0px
- full9999px
Elevation
Shadow tokens on a neutral card, at true blur and spread.
- inset0 0 #0000
- lift0 0 #0000
- overlay0 12px 24px rgb(0 0 0 / 0.16), 0 4px 8px rgb(0 0 0 / 0.08)
- ring0 0 0 1px #121212
- soft0 0 #0000
- xs0 0 #0000
DESIGN.md
Ships in your repo as DESIGN.md. The designer agent reads it before it may introduce a new visual pattern. This is the briefing, not a page about the briefing.
Ledger draws the interface like a ruled book. One warm 1px hairline marks every edge, every corner is square, and actions are flat ink. A large serif carries the headlines and a one-weight sans carries everything else. Colour only ever reports a status.
Read this before you add a screen, a component or a colour. The tokens live in
src/app/globals.css, the faces in src/app/fonts.ts, the kit in
src/components/ui/.
At a glance
| Theme | Light first: white canvas, warm grey lines, near-black ink. Dark is the same book on near-black with grey lines. |
| Type | Hedvig Letters Serif at 24px and up, Hedvig Letters Sans below that, IBM Plex Mono for ids, amounts and numbers. Weight 400 everywhere. |
| Colour | Ink and warm greys. Green, amber and red appear only as a status. |
| Shape | Radius 0 on everything. Only avatars, radio dots and status dots are round. |
| Depth | Flat. Hairlines do the work of shadows. Only menus, popovers, dialogs, sheets and toasts cast one. |
| Signature | Column rules and a warm tray in the hero, numbered section heads, ruled grids that share edges, and a double rule under every total. |
Principles
- One 1px warm hairline marks every edge. Need more separation? Add space, not depth.
- Every corner is square. Round is only for people and status dots.
- The serif only gets big: 24px and up. Everything smaller is the sans.
- Colour only reports a status. Everything else is ink and grey.
- Hover darkens a line to ink. Nothing lifts or glows, and a borderless item gets only a quiet warm fill.
Colour
Palette
| Token | Light | Dark | Role |
|---|---|---|---|
canvas (--background) | #ffffff | #0d0d0d | The page floor |
surface-soft | #f7f6f4 | #121212 | Section bands, sidebars, table heads |
surface-card (--muted) | #f3f2ef | #1c1c1c | Tinted fills: inactive tabs, hover rows, code |
surface-strong | #e6e4e0 | #232323 | Pressed fills, progress tracks |
surface-solid (--popover) | #f7f6f4 | #121212 | Menus, selects, toasts: always opaque |
card | #ffffff | #0d0d0d | The card fill |
surface-dark | #121212 | #161616 | The inverted panel |
hairline (--border) | #dbdad7 | #262626 | Every border and divider |
ink (--foreground) | #121212 | #fafafa | Headings and primary text |
body-strong | #242424 | #d4d4d4 | Emphasised body text |
body | #4a4a4a | #a1a1a1 | Running text |
muted (--muted-foreground) | #616161 | #878787 | Secondary text, captions |
muted-soft | #8a8a8a | #6b6b6b | Placeholders, disabled text, icons at rest |
primary | #18181b | #fafafa | The action colour |
accent (--accent-fill) | #121212 | #fafafa | The saturated accent: links, the accent badge |
accent-soft | #ebeae7 | #1c1c1c | The tint an accent label sits on |
danger (--destructive) | #a43b36 | #ff383c | Destructive actions and errors |
success, warning and error each come with a -soft tint for the back of a
badge or an alert: #006e30, #785a00 and #a43b36 in light, #22c55e, #eab308 and
#ff383c in dark.
Ledger's own variables, read by the landing hooks and the kit: --ground
(#f3f2ef light, #161616 dark).
How colour is used
- Ink (
--foreground, near-black) is text, primary buttons, focus outlines and every hover edge. The accent is ink too, so a featured thing gets an ink edge, not a colour. - Warm greys are structure:
--borderfor every hairline,--inputone step firmer on fields,--surface-softand--mutedfor quiet fills. --groundis the flat warm tray a framed thing sits on: the hero's product tray and the showcase pictures.- Green, amber and red appear only as a status: a tinted back, a coloured label and a 6px dot. Never a full fill.
Charts
accent-1 to accent-6 (also chart-1 to chart-6) are six series colours
ordered so neighbours stay distinguishable. The six chart colours are greys from
ink to pale, told apart by lightness alone. Past three series, label lines
directly and dash a forecast. Assign them in order; never let colour be the only
way to tell two series apart.
Dark mode
The palette is declared twice in src/app/globals.css: light on :root, dark
under @media (prefers-color-scheme: dark) and again on .dark, where the
class wins in both directions. @theme inline points every utility at those
variables, so bg-canvas is one class that follows the mode.
src/lib/design.ts sets the theme a first visit opens in: light here, because
Ledger is drawn in light first. The theme menu in the header offers light, dark
and system and remembers the choice.
Type
Faces
- Hedvig Letters Serif (
font-display): headings and display type. - Hedvig Letters Sans (
font-body): running text and interface copy (weights 400). - IBM Plex Mono (
font-mono): code, ids and tabular labels (weights 400, 500).
Hedvig Letters Serif is a warm, sturdy text serif that holds at 72px, and its optical size axis gives the few 18px uses a sturdier cut. Hedvig Letters Sans is its one-weight companion, so emphasis is size, colour and case, and IBM Plex Mono gives ids, amounts and numbers plain, slightly typewritten figures.
Every face loads through next/font/google in src/app/fonts.ts: downloaded at
build time and served from this app. Change a face there and here, together.
Scale
| Token | Size | Line | Tracking | Weight | Use |
|---|---|---|---|---|---|
text-display-xl | 72px | 1 | -0.025em | 400 | The hero headline, desktop |
text-display-lg | 56px | 1.04 | -0.025em | 400 | The hero headline, tablet |
text-display-md | 40px | 1.1 | -0.02em | 400 | Section headlines, the hero on phones |
text-display-sm | 32px | 1.15 | -0.015em | 400 | Section headlines on phones, big numbers |
text-title-lg | 24px | 1.33 | 0 | 400 | Page titles in the app |
text-title-md | 18px | 1.45 | 0 | 400 | Card titles |
text-title-sm | 16px | 1.5 | 0 | 400 | Small headings, table titles |
text-body-lg | 18px | 1.56 | 0 | 400 | Lead paragraphs |
text-body-md | 15px | 1.6 | 0 | 400 | Running text |
text-body-sm | 14px | 1.5 | 0 | 400 | Interface copy, table cells |
text-caption | 12px | 1.5 | 0 | 400 | Metadata, helper text |
text-caption-uppercase | 11px | 1.5 | 0.08em | 400 | Eyebrows above headings |
text-button | 14px | 1 | 0 | 400 | Buttons |
text-nav-link | 14px | 1.4 | 0 | 400 | Header and footer links |
Rules: one h1 per page. Display sizes are for marketing pages; the app tops
out at text-title-lg. Headings at title-lg and up are the serif at 400; a
heading set smaller falls back to the sans through the base layer. Small labels
are 11px capitals at 0.08em. Stats, prices and table cells use tabular figures,
and ids and amounts use the mono.
Space and layout
- Spacing tokens:
xxs4px,xs8px,sm12px,md16px,lg24px,xl32px,xxl48px,section96px (the gap between landing sections). - Content sits in
max-w-content(1280px) withpx-mdon phones andpx-lgfromsmup. Reading text stops atmax-w-measure(672px). - Generous space, small type: 96px between sections, 24 to 32px inside a cell, 14 to 15px text, 11px labels.
- Neighbours share one edge (negative 1px margins), so two boxes never draw a 2px line.
- Section heads sit on the left edge of the column. The hero stays centred.
- The hero sits in an 80rem column ruled on both sides; below that width the side rules drop.
Shape and depth
| Token | Value | Where |
|---|---|---|
rounded-xs | 0 | Not used by the kit |
rounded-sm | 0 | The landing page |
rounded-md | 0 | Toaster, the landing page |
rounded-lg | 0 | The landing page |
rounded-xl | 0 | The landing page |
rounded-pill | 0 | The landing page |
rounded-full | 9999px | Avatar, Badge, RadioGroup |
Every radius token is 0, so stack code that asks for a pill comes out square.
rounded-full stays for avatars, radio dots and status dots. Shadows xs, soft,
lift and inset are empty. The overlay shadow is real and only for things that
float, and focus is a square 2px ink outline 2px off the edge.
Components
Every component lives in src/components/ui/ and is written against the tokens
above. Import from there; never restyle a kit component inline to make a
one-off.
| Component | In Ledger |
|---|---|
Button | Square and flat, 14px sans at 400, sentence case, with a 1px border on every variant so sizes match. Primary is an ink block; outline is a hairline that turns ink on hover; danger keeps its red in the label until hover. |
Badge | A square 20px chip with a 1px border and 12px sans. Neutral is a hairline, accent is an ink edge, and status badges add a soft tint and a 6px round dot. |
Card | Square and flat in every tone. Default is the canvas colour with a hairline, soft and strong are warm fills with no border, and dark is near-black. Titles are the 18px sans. |
Input, Textarea, Select | 40px square fields with a firm --input line and the canvas fill. Focus turns the line ink and adds a 1px ink ring for a 2px edge, with no halo. Select content is a square hairline menu with the overlay shadow. |
Checkbox, RadioGroup, Switch | A 16px square checkbox that fills ink when checked. The radio is the one round control, with an ink dot. The switch is a square track with a square thumb that slides 16px. |
Tabs | Default is a warm tray where the active tab is a canvas sheet with a hairline. Line tabs sit on a hairline that turns ink under the active tab. |
Accordion | Items sit on hairline rules that turn ink on hover and when open. The question is the 16px sans, and a muted chevron turns over when an item opens. |
Dialog, AlertDialog, Sheet | A warm 6px tray holds a square hairline sheet over a flat scrim with no blur, and the dialog footer sits under a 4px tray rule. Titles are the 24px serif. Sheets are square canvas panels with a hairline on their inner edge and a ruled header and footer. |
DropdownMenu, Popover, Select content, Tooltip | Square hairline surfaces with the overlay shadow. Items highlight with a warm fill, labels are small capitals, shortcuts are mono. The tooltip is a tiny inverted ink tag with no arrow. |
Table | Ruled both ways like a ledger page: caps heads on a soft band, a column rule between cells, tabular figures, and a double ink rule above the footer total. |
Alert | A square panel with a 16px icon. Info is soft grey; success, warning and danger use a tint, a light status edge and a coloured icon. No side bar, no shadow. |
Avatar, Progress, Skeleton, Spinner, Separator, Kbd, Breadcrumb, Pagination, Field, Label, Toaster | Avatars are round; everything else is square. Progress is a 4px ink bar, separators are hairlines, Kbd is a mono key on a soft fill, pagination is square ghost cells with the current page outlined, labels are the 14px sans, and toasts are hairline panels with a status icon. |
The landing page
The landing page is the same in every design; the design decides how it looks.
Its sections carry data-slot hooks (listed at the top of
src/components/marketing/section-heading.tsx) that globals.css may style.
Ledger styles these:
hero-backdrop: the wash is gone. Two column rules on the 80rem column drop from the header hairline to the tray; below that width they drop out.hero-visual: a warm--groundtray with a hairline, its sides on the column rules.product-preview: a white, square, flat window on the tray, with a soft title bar.hero-eyebrow: a square hairline tag with a 6px green status dot, the only colour in the hero.section: every section opens on one full-width hairline. The one right after the hero and the logo strip drop it.section-headingandsection-eyebrow: heads sit on the left, and their eyebrows count in mono, 01 to 06. Showcase rows count 02.1, 02.2, 02.3.showcase-rowandshowcase-visual: entries split by one hairline, each picture on the warm ground.feature-gridandtestimonial: ruled tables with shared 1px edges and no gaps. A hovered feature cell raises its edge to ink, icons are outlined squares, and quotes are the 20px serif.stat-grid: big figures between a hairline above and a 3px double ink rule below, with caps labels.step-marker: outlined ink squares with a mono numeral.faq: the rule under an open or hovered question turns ink.logo-cloudandlogo: one ruled band of greyscale logos, with text wordmarks in the 24px serif, muted.cta-panel: a flat near-black slab with no wash and an inner grey frame 12px in.site-header,site-nav,brand-mark,site-footer: an opaque header with no blur, an ink underline on the current page, a serif initial in the mark, and a footer that opens on a double hairline.
Everything else is type, space and the kit.
Signed-in screens
The dashboard is a white canvas with a warm off-white sidebar split by a hairline, a 24px serif page title, hairline cards and tables ruled both ways with mono amounts and a double rule above totals. Sign-in is one white square card with a hairline on the warm ground, a serif title, 40px fields and a full-width ink button.
Do and don't
Do
- Separate regions with one 1px
border-hairline. If they need more separation, add space, not depth. - Keep every corner square. Use
rounded-fullonly for avatars, radio dots and status dots. - Set headlines at
text-title-lgand up infont-display, and everything smaller in the sans. - Put small labels in tracked capitals:
text-caption-uppercase uppercase text-muted. - Show a status with its colour and a 6px dot (
<Badge variant="success">). Keep everything else ink and grey. - Set ids, amounts and keys in
font-mono tabular-nums, and right-align money columns. - Answer hover on a bordered thing by darkening its line (
hover:border-ink), not by filling or lifting it. - Close a column of figures with the total rule (
border-t-[3px] border-double border-ink) above the total.
Don't
- Don't put a shadow on anything that does not float.
shadow-overlayis for menus, popovers, dialogs, sheets and toasts only. - Don't round a button, card, input, badge, image or tab. No
rounded-md, norounded-lg. - Don't add a brand colour, a gradient, a glow or a backdrop blur.
- Don't reach for
font-medium,font-semiboldorfont-bold. They render at 400 here. Use size, colour or capitals. - Don't fill a whole block with a status colour. Tint the back
(
bg-success-soft), colour the label and the dot. - Don't use dashed or dotted rules. The only dashed line is the empty state's placeholder.
- Don't stack two lines where one will do. No bordered card inside a bordered
panel; let neighbours share an edge (
-ml-px,-mt-px). - Don't paste
hover:bg-accent hover:text-accent-foregroundas is. Here the accent is ink, so usehover:bg-surface-card hover:text-ink.
Working in this design
Pasting from shadcn/ui. The token layer carries shadcn's variable names, so
most snippets render unmodified. Two utility names mean something else here:
bg-muted becomes bg-surface-card, and bg-accent / text-accent-foreground
(a hover surface upstream) become bg-surface-card / text-ink. Here muted
is a text colour and accent is the saturated accent.
Adding a token. Add it to all three palette blocks in globals.css (light,
the dark media query, .dark), map it in @theme inline, and document it in
the palette table above. Check contrast in both modes.
Adding a component. Check the kit first: most needs are a variant of
Button, Card or Badge, or a composition on the page. A new component gets
its own file in src/components/ui/, token classes only, a data-slot, and a
row in the Components table here. The designer subagent owns this.
Build a repo in Ledger
The builder opens with Ledger picked. Pick your batteries and the design is applied to every template before the zip is built.
