Good morning
| Date | Payee | Amount | Status | |
|---|---|---|---|---|
| 07/09 | Baxter Rental | −$3,200.00 | POSTED | ⋯ |
| 07/08 | Schwartz Prod. | +$18,500.00 | PAID | ⋯ |
| 07/06 | Ashland Util. | −$412.55 | PENDING | ⋯ |
| 07/03 | Electra FW | +$44,000.00 | OVERDUE | ⋯ |
Ze'ev Waismann · Design Systems
zwaismann.com →How I design productive software: one shell, two dialects. A data/ledger dialect and a studio/material dialect, distilled from two products I build and maintain.
A design system for productive software: bookkeeping tools, studios, dashboards, ops apps, internal command centers. Re-derived from the shipped source of Even (accounting OS, the data/ledger reference) and Content Machine (multi-channel campaign studio, the studio/material reference), aligned with the industry shell popularized by Linear, Slack, Plaid, Supabase, Vercel, Claude, and Codex.
This is not for brand, portfolio, or marketing sites. Those are a different discipline (see Section 1). Functional apps may be quietly cinematic; they are never theatrical.
How to use this page: you do not read all of it. Do the 5-second dialect pick, copy the starter block, then dip into the section you need. Everything after the starter is reference.
One system, two dialects. Ninety percent is shared (the shell, the motion contract, color-as-state, the bans). The last ten percent forks. Pick in five seconds:
| Date | Payee | Amount | Status | |
|---|---|---|---|---|
| 07/09 | Baxter Rental | −$3,200.00 | POSTED | ⋯ |
| 07/08 | Schwartz Prod. | +$18,500.00 | PAID | ⋯ |
| 07/06 | Ashland Util. | −$412.55 | PENDING | ⋯ |
| 07/03 | Electra FW | +$44,000.00 | OVERDUE | ⋯ |
If the app has both a data side and a studio side (an ops tool with a media step), the shell and everything in Sections 3-6 stay identical; you switch page-chrome pattern per route (Section 9).
| Dial | Data default | Studio default | Why |
|---|---|---|---|
| DESIGN_VARIANCE | 4 | 5 | Structure stays stable; sections do not reinvent layout per page |
| MOTION_INTENSITY | 3 | 4 | Motion signals state, never entertains. Studio adds one tactile progression gesture |
| VISUAL_DENSITY | 6 | 6 | Linear-density ground; empty space is a separator, not gallery air |
Source: Even DESIGN.md, Content Machine DESIGN.md. These are starting dials, not a cage.
| Kind | Goal | References | Default tool |
|---|---|---|---|
| Functional app | Usability, density, speed, clarity. Get work done. | Even, Content Machine, Linear, Slack, Supabase, Vercel, Claude | This doc + functional-app skill + per-project DESIGN.md |
| Brand / portfolio / marketing | Emotion, narrative, cinematic presence. Be a window into a brand. | Electra HEX, Tokyo Midnight, Allentown | taste skill (landing/portfolio) or impeccable for dense marketed product; brand/motion labs |
Hard rule: do not pour portfolio motion languages (GSAP hero stages, hex flood grammar, Awwwards scroll theater) into a bookkeeping or studio tool.
This is not:
- A replacement for a per-product DESIGN.md. Each product still owns its tokens, voice, and domain rules; it inherits this doc and records only deltas.
- A license to clone Even jade or Content Machine amber onto every app. Roles transfer; palettes stay product-specific.
- Guidance for Electra HEX / film marketing.
- An AI Agent Masterclass chapter. Masterclass is agent harness; this is product UI.
Six principles, stated verbatim in both apps' contracts. They apply to every new functional product unless the brief explicitly overrides them.
decisions.md:9.)decisions.md:15.)decisions.md:19.)design-system/DESIGN.md:125; Even conventions.md:85.)The shared skeleton. Brand colors and type faces change per product; the geometry of the shell does not. No top bar: everything the top bar used to own (identity, search, account, entity switch) lives in the rail. Both Even and Content Machine deleted their top bars.
No top bar. The rail owns identity, nav, and account; each page registers a title + one primary action and ships content only. The AI presence floats (FAB ↔ Medium ↔ Full).
Even (data dialect): left AppRail 232px open / 64px slim. Two-column grid auto 1fr; Ledger AI is a single floating morphing surface (FAB to Medium to Full), not a persistent column. Rail holds wordmark, entity switcher, cmd-K ask-or-create, seven nouns, collapse button, account menu.
Content Machine (studio dialect): left AppSidebar 220px open / 64px collapsed (four workspace nouns). Campaign pipeline steps live in a horizontal CampaignChrome top strip inside the main column, not the rail. Work surface + right inspector + bottom material dock; a full-height Content Library panel sits beside the material stages.
| Owns | Shell | Page |
|---|---|---|
| Persistent left navigation | Yes | No |
| Collapse / slim state (remembered) | Yes | No |
| Wordmark / product identity | Yes | No |
| Account / entity / theme | Yes | No |
| Global search / create (cmd-K) | Yes when present | No |
| Page title + one primary action | Shell renders slot; page registers | Registers only |
| Domain content, tables, editors, stage strip | No | Yes |
| Per-page filters, modes, toolbars | No | Yes (inside content) |
Fat shell preferred. Pages ship content only. Cohesion becomes structural, not a review discipline. Even implements this with a PageChrome React context: the page registers its title and controls, the shell renders the header.
| Mode | Even | Content Machine | Guidance |
|---|---|---|---|
| Expanded | 232px | 220px | 220-240px |
| Collapsed / slim | 64px | 64px | Icon strip; labels via title / sr-only |
| Mobile drawer | 280px overlay + backdrop | Overlay + backdrop | Hamburger opens; route change / backdrop / Esc closes |
One permanent handle toggles Expanded ⇆ Slim (state remembered). Below ~768px the rail becomes an overlay drawer that closes on navigate, backdrop, or Esc.
g then key navigation is welcome for power users (Even pattern).Adopted product-wide from Even's L4 "recede grammar." Use for rails, panels, sections, More groups, inspector tools, and the studio stage strip.
| Rule | Detail |
|---|---|
| Duration | House comp register (Even 560ms; studio 150ms for smaller disclosures) |
| Easing | One house curve only |
| Handle | Permanent. Never hide the control that reopens a fold |
| Honest summary | When folded, show a real summary (count, status, one-line lead), never a mystery chevron |
| Memory | Persist open/closed (localStorage or account prefs) |
| Agency | Nothing folds itself. User action only (a sanctioned AI may open its own surface) |
| Nesting | One collapse layer only. Once a section opens, show fields directly; never disclosure-in-disclosure |
| Implementation | Prefer grid-template-rows: 0fr -> 1fr for height animation, not layout-property thrash |
One disclosure vocabulary. Both apps standardize a single DisclosureRow primitive: short uppercase title, helper line, chevron chip that rotates and brightens to the accent on open, a soft focus ring. Do not invent a second disclosure look per screen.
Studio stage strip is a two-axis fold (Content Machine CampaignChrome): (1) labels vs icons densify, auto below 1200px, user-overridable; (2) phase groups collapse when inactive to a bordered count chip, click to expand in place without changing stage. Reuse this pattern for any pipeline/stepper that outgrows its width.
One ease, a small set of durations, reduced-motion floor. "One ease" is the stated rule for new work. Both shipped apps still carry legacy back-compat tokens the rule explicitly deprecates; do not introduce new legacy motion, and when the codebase mixes it, the rule is the target, not the old code.
| Token | Value | Use |
|---|---|---|
--ease-fc |
cubic-bezier(0.22, 1, 0.36, 1) |
The only ease. Soft out, no overshoot |
--d-quick |
220ms | Press, hover, toggle, focus, toast, rollover reveal |
--d-comp |
560ms | Filter expand, dropdown, accordion, tabs, rail width, folds |
--d-cinema |
900ms | Modal, drawer, route-level sheet, hero reveal |
| Ambient (sanctioned only) | --d-breath 4800ms / alert 2600ms |
A single living AI presence, if the product has one |
| Token | Value | Use |
|---|---|---|
--cm-ease |
cubic-bezier(0.4, 0, 0.2, 1) |
House calm ease |
--cm-ease-out |
cubic-bezier(0.16, 1, 0.3, 1) |
Reveals |
--cm-dur-fast |
120ms | Content-editable focus, micro |
--cm-dur-ui |
150ms | Disclosures, hovers, selection rings |
--cm-dur-advance |
180ms | The one progression gesture: primary button translateY(-1px) + brightness |
Studio screens hard-swap (no route theater). The studio register is slightly more tactile than the data register: it earns exactly one physical progression gesture (lift + brighten) and one lock ceremony. It does not earn more.
Bans (both dialects): no second curve, no fourth data-register duration, no bounce/elastic, no animating layout properties, no opacity: 0 first-paint fades that hide content, no ambient motion except one explicitly designed presence.
@media (prefers-reduced-motion: reduce) {
/* Zero (or ~0.001s) every duration token globally.
Nothing may reintroduce motion that bypasses this. */
}
Each product picks its own ground and accent. The roles stay constant; the palette does not. Before painting an accent, name the state.
| Role | Even (data) | Content Machine (studio) | Rule |
|---|---|---|---|
| App ground | Tinted near-neutral, hue ~260 | Warm near-black #141310 |
Never pure #000 / #fff |
| Raised surface | surface ladder | panel / panel-2 ladder | Depth via ladder + hairlines, not shadow |
| Ink ramp | ink -> ink-5 (5 tiers) | ink -> ink-3 (3 tiers) | Quiet hierarchy |
| Hairlines | line / line-soft | line / line-2 | Primary separators |
| Brand accent | jade / pos-green on the wordmark | amber (also = active state) | Wordmark + active + primary; under ~10% of surface |
| Positive / done | pos green (income) | sage (locked / approved; no separate green) | |
| Negative / danger | neg red | clay (destructive / error only, never positive) | |
| Warning / needs-you | warn amber | clay or amber, named | Earned, not decorative |
| AI presence | accent-ai mint | (product-specific) | Quiet signal, not chatbot chrome |
Exactly three semantic state colors is a good ceiling (Content Machine: amber / sage / clay, no fourth accent, amber is also the brand hue). Data apps add money semantics (income vs loss vs pending) on top.
Section identity hues (data apps, optional): each primary nav noun may carry a wayfinding accent (Even: Home emerald, Transactions blue, Invoices violet, Reports cyan, Contacts rose, Projects olive, Settings deliberately neutral). Applied to rail active/hover and a dot beside the page title only. Never on money, never colliding with pos/neg/warn or the AI accent.
This is the one place the two references genuinely diverge, and it is the first scaffolding decision:
data-theme="light" in the root shell, then flips to the stored preference before paint, and supports light + dark + an editorial paper theme (used for the public invoice canvas). Money and tables read well in light.Pick your stance before you pick tokens. If you support both themes, stamp data-theme on the root and reach every color through a token so dark mode never breaks (Even rule: no raw color literals in components). If you go single-surface, say so in the project DESIGN.md so no one adds a half-built toggle later.
| Role | Even | Content Machine | Guidance |
|---|---|---|---|
| UI / body | Inter 14px | Inter 14px | Default product face, base 14px, dense |
| Numbers / money | Inter, tabular-nums | JetBrains Mono for machine values | Align columns; never money in mono (Even); never mix mono/sans in one numeric column |
| Taxonomy labels | JetBrains Mono uppercase, tracked | JetBrains Mono uppercase, tracked wide | Eyebrows, th, pills, wordmark. Wide tracking is what marks mono as taxonomy, not prose |
| Machine metadata | JetBrains Mono | JetBrains Mono | IDs, account masks, timecode, keyboard hints |
| Display / identity | none (Inter only) | Fraunces, sparingly | Route titles only; never inside compact controls |
Weight discipline: 400 row data, 500 stats / totals / stat-card values, 600 reserved for the single hero amount per page (never on repeated stat cards or table cells; Even caught a real drift here). Numerics use font-variant-numeric: tabular-nums, not font-feature-settings: 'tnum'.
Money negatives: Unicode minus U+2212, not a hyphen-minus. GAAP parentheses on totals for accounting products.
Every interactive control ships all seven. Hover and rollover arrive on the quick register (150-220ms); focus-visible mirrors hover for keyboard users.
| Surface | Hidden at rest | Revealed on intent |
|---|---|---|
| Data rows | Extra actions | Row action menu (portal chevron whenever 2+ actions) |
| Figures (Even) | Sparkline + Explain popover | On hover / focus of the figure |
| AI FAB (Even) | Question label | On hover when amber |
| Asset tiles (Content Machine) | "Use" overlay | On hover |
Reveal on the quick register (150-220ms). Keyboard users get the same affordances on focus.
Status pills are 1-2 words, uppercase mono when the product uses a mono taxonomy: PAID, OVERDUE, LOCKED, SCHEDULED, IN REVIEW. A pill is state, not a sentence. Show a given state once per row or card; do not duplicate the same status across neighboring panels.
The default order for a data/list page. Hairlines, not cards. Dense (~7px cell padding). One hero number, weight 600, once per page.
Collapsible stage strip on top, then work-surface-left / inspector-right, material in a bottom dock. Cascade before nesting; browser-width, not centered; no card-in-card.
Cascade before nesting. Avoid nested scrollbars; prefer responsive wrapping, collapsible option cards, and full browser width. Cards are for repeated items and tools; never card-in-card, never wrap a whole page section in a card. Workspaces are browser-width; the one documented exception is a source-intake screen (centered ~840px column).
| State | Behavior |
|---|---|
| First run | One clear next action; no fake charts full of zeros pretending to be data |
| Loading | Skeletons or calm progress on the real layout chrome; no full-page spinner thrash |
| Empty list | Noun + one primary create / connect action |
| Partial AI failure | Degrade gracefully; never invent numbers or confident prose without evidence (Even Morning Brief rule; Content Machine keeps AI output inspectable, provenance visible) |
| Destructive confirm | In-app confirm dialog only (Even useConfirm()); window.confirm is banned |
| Busy actions | aria-busy, block double-submit, honest label ("Saving...") |
| Band | Width | Shell behavior |
|---|---|---|
| Desktop | >= ~1024px | Full rail + multi-column workspaces |
| Tablet | ~720-1023px | Rail may slim or overlay; secondary panels stack |
| Mobile | < ~768px | Rail becomes overlay drawer (280px); stat grids 2-up; tables scroll-x preserving fixed columns; AI becomes a full sheet |
Content Machine named breakpoints: drawer 1000 (library becomes overlay), fold 1180 (secondary panels stack), wide 1360 (grid opens three composer zones).
table-layout: fixed); do not reflow money columns into stacked cards unless the product explicitly designs a mobile list mode.Reality note: both reference apps treat mobile as inherited, not freshly redesigned each pass. Meet the non-negotiables; you do not owe a bespoke mobile redesign on v1.
Every new functional app ships these on day one of any public surface. Missing tab icons and link previews are a recurring regression, and the two reference apps each ship a partial version, so treat this as the target, not something to copy verbatim from Even or Content Machine.
| Asset | Spec | Notes |
|---|---|---|
| Favicon | SVG or 16/32/192/512 PNG | Monogram / mark that reads at 16px. In Next.js under a basePath, use the app/icon.svg file convention, not a hardcoded /icon.svg that 404s |
| Apple touch icon | 180x180 PNG | apple-icon or explicit metadata |
| Title template | "%s · Product" |
Default product name when no page title |
| Description | One calm sentence | Not marketing spam |
| Open Graph image | 1200x630 | Product card. Generate with next/og ImageResponse |
| Twitter card | summary_large_image |
Match OG |
metadataBase |
Absolute product URL | Required for correct OG URLs in Next.js |
Product ground, product ink, brand accent on the mark only. No screenshot spam, no six feature bullets, no gradient mesh. Keep the card as quiet as the product.
Keep OG as quiet as the product. No screenshot spam, no six feature bullets, no gradient mesh. Even's card: Even. wordmark with a jade period, "Accounting without theater.", evenlabs.ai. Do not leak entity branding into product link previews (icons and OG should carry the product mark, not a client / entity brand).
Anti-slop (shared with brand systems): pure #000 / #fff as large fields; gradient text; glassmorphism as a default surface treatment; colored left-border accent stripes as decoration; the huge-number + tiny-label hero cliche repeated as a grid; identical icon-card feature walls; em dashes; emoji in product UI.
Functional-specific: color used as decoration; card chrome as the default table treatment (prefer hairlines for ledgers); nested cards / nested disclosures; new motion curves or bounce; ambient animation everywhere (one living presence max, if any); pages re-implementing the whole shell; nav that lists every engine concept; window.confirm; raw color literals in components (tokens only); shipping without favicon + OG on public routes; a half-built theme toggle in a single-surface studio app.
Do this before inventing screens.
window.confirm).DESIGN.md that inherits this doc and records only product-specific deltas.tokens.css)/* Fill the four bracketed groups; keep the role names. Reach every color
through these tokens - no raw literals in components (breaks theming). */
:root {
/* --- ground + ink (never pure #000/#fff) --- */
--bg: /* data: tinted near-white or hue-260 near-black; studio: warm near-black e.g. #141310 */;
--surface: /* one step raised */;
--surface-2:/* two steps raised */;
--ink: /* primary text */;
--ink-2: /* strong secondary */;
--ink-3: /* labels / secondary */;
--line: /* primary hairline */;
--line-soft:/* faint hairline */;
/* --- accent + semantics (each = a state, not decoration) --- */
--brand: /* wordmark + active nav + one primary action family; under ~10% of surface */;
--pos: /* income / done / approved */;
--neg: /* loss / overdue / destructive only */;
--warn: /* pending / needs-you (earned) */;
--accent-ai:/* one quiet AI signal, if the product has AI */;
/* --- motion (one ease; do not add a second) --- */
--ease: cubic-bezier(0.22, 1, 0.36, 1); /* data F+C; studio: cubic-bezier(0.4,0,0.2,1) */
--d-quick: 220ms; /* press, hover, focus, rollover (studio: 150ms) */
--d-comp: 560ms; /* folds, dropdowns, rail width (studio: 180ms) */
--d-cinema: 900ms; /* modal, drawer, full sheet (studio: hard-swap, omit) */
}
@media (prefers-reduced-motion: reduce) {
:root { --d-quick: 0.001ms; --d-comp: 0.001ms; --d-cinema: 0.001ms; }
}
// Rail owns identity + nav + account. Page ships content only.
<div className="app-root"> {/* fixed inset-0, overflow hidden, bg=var(--bg) */}
<aside className={rail(slim)}> {/* 220-232px open / 64px slim; sticky, border-right hairline */}
<Wordmark /> {/* product mark, quiet */}
<EntitySwitcher /> {/* if multi-entity */}
<AskOrCreate /> {/* cmd-K search + quick create, if present */}
<Nav nouns={FIVE_TO_SEVEN} /> {/* owner's nouns; active = fill wash (+ optional section hue) */}
<Fold label="More" /> {/* rare destinations; permanent handle */}
<CollapseButton /> {/* toggles slim; persist to localStorage */}
<AccountMenu /> {/* pinned to foot: theme toggle, sign out */}
</aside>
<main className="main-col">
<PageChrome /> {/* shell renders the header slot; each page REGISTERS title + 1 action */}
<Outlet /> {/* data: list/table | studio: stage strip + work surface + inspector */}
</main>
{hasAI && <FloatingAI />} {/* FAB <-> Medium <-> Full morph; the ONLY ambient motion */}
</div>
*-design-system package once patterns stabilize; do not block v1 on it, and verify against the shipped app, not a stale package.DESIGN.md templateEvery product gets a DESIGN.md that inherits this doctrine and records only its deltas. Copy this skeleton on day one; fill the brackets. Even and Content Machine both follow this shape.
# <Product> - DESIGN.md
> Inherits the functional-app doctrine (this doctrine).
> Records ONLY product-specific deltas. When this and the doctrine disagree, this wins
> for this product; if a pattern should be house law, promote it to the doctrine instead.
North star: <one line, e.g. "accounting without theater">
Dialect: data | studio (doctrine Section 0)
Theme: light-default multi-theme | single warm-dark, no toggle (Section 6.1)
Dials: VARIANCE / MOTION / DENSITY e.g. 4 / 3 / 6
## Tokens (deltas from the starter in Section 15.1)
Ground: <bg + surface ladder>
Ink: <ramp, how many tiers>
Brand: <accent - and the ONE state/role it names>
Semantic: pos / neg / warn (+ studio: sage / clay) - the STATE each color means
## Type
UI: <face + base size>
Taxonomy: <mono face>
Display: <optional; route titles only>
## Shell
Rail nouns: <5-7 owner nouns>
Recede: <what lives under Settings / More fold, never deleted>
AI: none | <one floating presence + what it does>
## Motion
<inherit data F+C or studio register; note any product-specific token>
## Domain rules (product-specific, NOT in the doctrine)
- <e.g. money = integer cents, GAAP parens; bank-vs-books; lock ceremony; timecode>
## Bans beyond the universal list
- <product-specific>
The doctrine is design; this is the plumbing an agent should not have to re-derive. Lean on purpose - it names defaults, it is not a framework manual. Override per project brief.
| Concern | Default |
|---|---|
| Framework | Next.js (App Router) + React + TypeScript |
| Styling | Tailwind for utility layout; design tokens as CSS custom properties on :root / [data-theme], reached via var(--...). No raw color literals in components (breaks theming) |
| Package manager | pnpm if the repo uses it, else bun. Never npm / yarn |
| Fonts | Self-host via next/font (or @font-face + font-display: swap). Never a Google Fonts <link> in production |
| State persistence | Rail slim/expanded, fold open/closed, Customize prefs -> localStorage, keyed per product (e.g. even-rail); account prefs when signed in. Restore on load |
| Icons / OG | app/icon.* + app/opengraph-image.tsx file conventions (basePath-safe); metadataBase absolute URL; "%s · Product" title template |
| Confirm | An in-app useConfirm() / ConfirmDialog primitive. window.confirm is banned |
| Deploy | Vercel or similar; keep secrets in a manager, never in the repo |
| Verify | Run the feature at the user's layer before calling it done; do not trust a green build alone |
| DS package | Extract *-design-system only once patterns stabilize; the shipped app stays source of truth (packages lag, see Section 16.2) |