DESIGN.md
Canonical product, visual, interaction, and anti-pattern contract. Read it before producing Atlas work.
Use the tree to inspect foundations, component families, applied kits, and quality gates without leaving the catalog.
Atlas is an organisation ledger for workforce management: every organisational fact is stored as a time-bounded event, not a mutable record. The system runs on one Flush accent, warm Plum neutrals, and the Newsreader display serif, and is governed in layers from tokens up to applied kits.
Canonical product, visual, interaction, and anti-pattern contract. Read it before producing Atlas work.
How to generate or extend Atlas components without treating an app screen as the whole system.
Package governance, layer map, preview manifest, and the reuse workflow.
Flush is the action color. Use 600 for the primary action, focus marker, active row, and allocation segments; keep it out of decorative page washes.
Plum is the warm charcoal neutral. The dark sidebar and overlays sit at plum-900, the app canvas is plum-100, and the default hairline is plum-300. Surfaces are flat plum tints, never neutral grey.
Category tints give up to five distinguishable series hues for work-group categories, matrix split bars, and Gantt bars. The on-dark status variants are brighter, for dots and markers on plum-900 surfaces (sidebar, scrubber).
Newsreader carries display hierarchy and tabular numbers; Hanken Grotesk carries UI copy; IBM Plex Mono is reserved for references, dates, hashes, and money.
Atlas spacing is a compact operational scale: small steps for dense controls, larger bands for section rhythm, and explicit mobile breathing room where touch targets need it.
Use small radius for controls, medium radius for operational surfaces, pill radius only for compact metadata, and plum-tinted shadows only when elevation clarifies hierarchy.
Shadows are plum-tinted and functional. Use them to separate overlays or raised panes from the workspace, not as a decorative glow layer.
Use the full lockup for package identity, the inverse lockup on plum surfaces, and the compact mark for tight chrome or identity anchors.
assets/atlas-logo.svgassets/atlas-logo-inverse.svgassets/atlas-mark.svg
The launch icon is its own asset, not the 40px mark scaled up. It uses a plum product canvas, ledger geometry, and a Flush mark built from paths.
Flush-600 · #E03A63
The build folder mirrors the source marks used by the packaged app. Keep parity between source SVGs and runtime exports.
Atlas icons are 24px line glyphs with 1.5px strokes and currentColor inheritance. Use them as quiet operational signals, not decorative badges.
All 87 source symbols are rendered here from assets/icons.svg. The grouping is for scanning only; implementation should reference the exact symbol ID shown beside each glyph.
Keep icons subordinate to labels. Size them by surface density, preserve currentColor, and avoid introducing one-off filled variants.
Atlas moves the least amount that still reads as responsive. Transitions are short and token-timed; nothing bounces, scales, or spins forever. Motion confirms an action or eases a surface in; it never decorates. Hover over the rows and replay the panel below to see each rule live.
0.1 to 0.18s ease on colour, border-colour, background, and box-shadow. Nothing slower, nothing infinite. Hover darkens borders to plum-700, fills ghost buttons to plum-200, and washes rows to plum-100.
Move the pointer over each to see the only sanctioned hover treatments.
Panels, drawers, and the command palette enter with a soft fade and a 4px rise — opacity 0 to 1, translateY(4px) to 0, over roughly 0.16s. No scale, no slide-in from the edge.
Press is a colour shift only, never a transform — no shrink, no bounce. The single exception to the no-loop rule is a tiny indeterminate spinner at flush-600 for genuine waits.
Hold the button down: the fill shifts to flush-700, the box never moves.
When the operating system requests reduced motion, Atlas drops the rise and fade and the segmented slide, keeping only the colour transition at 120ms or less. Nothing essential depends on movement.
Honoured through a single media query: entering surfaces appear instantly, the segmented thumb snaps, hovers still recolour.
Atlas states facts; it does not sell. Copy is calm, operational, and precise — mostly declarative noun phrases and short verbs. The same rules govern labels, buttons, hints, and event history so every surface reads in one voice.
Declarative and operational. Report the state of the organisation; never market it. Second person stays rare.
Sentence case for body, buttons, and chips. Uppercase with 0.16em tracking for eyebrows and column headers. British English throughout.
organisation · allocation · cancelled · prioritise · licence. Not organization, canceled, prioritize.
Numbers are first-class and always tabular. Headline metrics use the display serif; everything else follows fixed formats.
Every change reads as a ledger entry, never a destructive edit. Past tense for history, future tense for planned events.
The regressions an agent must not reach for when generating Atlas artifacts. Each is paired with the canonical move, so the list reads as a correction, not just a ban. Markers are line SVGs, never emoji — the page holds itself to its own rules.
Backgrounds are flat plum tints; Flush is the single accent.
Newsreader is the only display face; icons are line SVGs; Atlas does not market itself.
Hover is colour-only, shadows are plum-tinted, nothing decorates.
Real numbers only; product surfaces stay clean; applied kits are evidence, not the system.
Accessibility is a property of the tokens, not a later pass. Every focusable element wears the same ring, text colours meet WCAG AA on their intended backgrounds, every action is reachable from the keyboard, and touch surfaces respect a 44px minimum.
Focus-visible is the flush ring — flush-600 at 18% alpha — on every focusable element, identical across buttons, inputs, and selection controls. Shown forced below; tab through any real control to see it.
Text pairs target at least WCAG AA (4.5:1). The brightest accent, flush-600, is a fill behind bold or large text; flush-700 carries small text and borders on light surfaces.
Every surface is fully operable from the keyboard. The command palette is the global entry point; Escape always backs out; Tab follows reading order.
On touch, targets are at least 44px, body copy at least 14px, and meta at least 12px. Hover-only affordances always carry a visible touch equivalent.
The Core command primitive. Primary, secondary, ghost, danger, and icon variants share height, focus ring, and loading spinner so every entry point reads the same.
Rendered directly in the catalog as native markup — 28px tall, hairline border, flush focus ring. Primary uses --at-flush-600; default sits on white; ghost is transparent until hovered.
Same ring on every focusable element: --ring-accent.
Compact metadata primitives used in tables, lists, and dense headers. Variants are driven by props instead of new components.
Two-colour person encoding everywhere: employees read plum, contractors read flush. The same anchors appear in the chip, the avatar, and downstream in AllocationBar / matrix split bars.
The bare input primitives the rest of the system composes. Field wraps these with a label, hint, and validation; here they stand alone so any surface can reuse them. One baseline: 30px tall, hairline border, small radius, Flush focus ring, token colours only. Flush stays scarce, surfacing only in the focus ring and in the on state of selection controls.
Each control is a self-contained component in components/core/, drop-in like Button and Field and consuming the same tokens. Field composes these for labelled drawer forms. One card per file below; variants live inside their component's card.
Text, numeric, search, and date — all one component. Default on white, Flush focus ring, Flush-700 border on error, tinted fill when disabled.
variant="numeric"
Serif tabular figures, right-aligned, for headcount, allocation, and money.
icon prop)
A leading icon shares the control box; the wrapper, not the input, owns the focus ring.
First iteration uses the native date control on the same .at-control box, so the OS picker opens on click. A fully styled Atlas calendar is a planned follow-up.
Native select with a token chevron; appearance reset so it matches the input box.
Resizes vertically only; use for notes and rationale on an event.
Checked fills Flush; the tick is drawn in token white. The type="radio" variant gives the round single-choice control with a Flush dot.
type="radio"
A few mutually exclusive options inline; the active option rises on a neutral pill that slides, so Flush stays reserved.
Drawer form primitives promoted from the workforce app. Field, FieldRow, Hint, and ValidationMessage share label, hint, and validation rhythm so drawers feel consistent across event types.
Promoted from the event drawer pattern: label above, 30px control, 11px hint, Flush focus/error states, and compact equal-column rows. See the Usage rules below.
Label sits above the control; the control is 30px on white with a hairline and the flush focus ring; the hint is one sentence below at 11px, tied to the field.
Field is label and hint; the control carries the border and ring. Required marks controls that block commit; an error turns the control border flush-700 and reddens the hint. Disabled dims the whole field.
For related pairs or short triples; keep textareas and allocation controls as full-width Fields.
Section-level conditions such as allocation totals; prefer copy that includes the current value. Hint is preventive; error/ValidationMessage report a failure.
components/core/ValidationMessage.jsx
Transient post-commit result: success, error, or ambient state change. Top-right and host-managed; 4s for success/info, 8s for error, and never auto-dismiss an error without an onDismiss.
Persistent state the user must keep while working: historical view, non-production data, or connection loss. Sits at the top of a section or page.
components/feedback/Banner.jsx
Only for a concurrency conflict needing a reload or discard decision. Replaces the whole drawer body and never times out; not for validation or network errors.
components/feedback/ConflictBanner.jsx
Stacks per-work-group capacity %, colour-coded by category. Under 100% leaves a hatched plum tail; over 100% saturates as a warning.
components/data/AllocationBar.jsx
The bar in its three states, with the optional total badge (showTotal) on the right — the bar's own balanced / under / over readout. Names, roles, and category chips belong to the Allocation summary surface.
A colour swatch, the work-group label, and its share. The legend primitive composed by Allocation summary and diff.
components/data/AllocationChip.jsx
Monospace pill for literal system values: cost centres, NetSuite ids, account refs. Reads as a verbatim code, distinct from prose and from tabular numbers.
CC-TR-002
NS-2078
EVT-014
One surface over the shared AllocationBar with parts you turn on or off: an optional identity, the balance badge, and the category chip row. It covers both the directory person row (identity + bar + balance) and the breakdown strip (bar + balance + chips), so the bar is never re-implemented.
Chips off. The same surface that fills a people/team list.
Identity off. The same surface that shows a single entity's dimension split.
The impact-preview surface shown before committing a re-allocation event: before and after capacity as two bars, with per-dimension rows and a signed delta on each changed group.
components/surfaces/AllocationDiff.jsx · applied instance ui_kits/app/allocation.jsx
A dark plum-900 component inside the otherwise light system. Nav rows read plum-400; the active row washes flush at 16% with a 2px flush marker on the left edge.
components/surfaces/Navigation.jsx · applied instance ui_kits/app/app-shell.jsx
The topbar "as of" control that opens the scrubber. A dot encodes whether the global view date is today, in the past, or in the future.
A dark plum-900 overlay component. The timeline carries ledger events colour-coded by kind, a flush today cursor, a legend, and jump chips.
components/surfaces/Scrubber.jsx · applied instance ui_kits/app/overlays.jsx
A row × column grid shell: headers on both axes and count cells with an optional employee/contractor split bar and meta; empty cells stay muted. You give it columns and rows. Shown here with function × work-group sample data; the live function × work-group matrix in the app is an instance bound to the ledger.
components/surfaces/Matrix.jsx · applied instance ui_kits/app/overview.jsx
| Engineering14 people | Finance5 people | Design4 people | |
|---|---|---|---|
| Core Platform | 8 |
— |
2 |
| Country Italy | 4 |
3 |
— |
The dense table shell: uppercase 11px column labels, hairline rows, tabular numerics, right-aligned numeric columns, and a PersonCell helper. You give it columns and rows. The applied people, events, reporting, and contractor-cost tables in the app are instances of this chrome.
components/surfaces/Table.jsx · applied instance ui_kits/app/detail-screens.jsx
| Person | Function | Team | FTE |
|---|---|---|---|
| MFMarco Ferraro Director of Product | Product | Core Platform | 1.0 |
| APAlina Petrov Software Engineer II | Engineering | Country Italy | 0.8 |
| ENEva Novak Finance partner | Finance | Revenue Ops | 1.0 |
Joins KPI tiles into one hairline-bordered rectangle: uppercase label, display-serif numeral, and optional meta line. A composed data layout — you give it the stats; the applied overview KPI strips in the app are instances.
components/surfaces/StatStrip.jsx · applied instance ui_kits/app/overview.jsx
A Gantt layout shell: a month axis, labelled lanes, and bars positioned by date range with a flush today marker. You give it months, rows of bars (positions 0-100), and today. Shown with assignment / role / manager sample lanes; the applied person and team timelines in the app are instances bound to the ledger.
components/surfaces/Timeline.jsx · applied instance ui_kits/app/detail-screens.jsx
A vertical timeline of event cards — kind + icon, mono event id, date, title, key/value relationship badges, and an author with relative time. You give it events; planned (future) events get an amber node ring. The append-only ledger event log in the app is an instance; every change is a new record, never a destructive edit.
components/surfaces/EventLog.jsx · applied instance ui_kits/app/detail-screens.jsx
A titled key-value card for identity and detail side panels. You give it rows of label/value; the value is a free node — plain text, a Chip for status, or a CodeValue for reference codes. The applied identity panels in the app are instances.
components/surfaces/DetailList.jsx · CodeValue.jsx · applied instance ui_kits/app/detail-screens.jsx
CC-TR-002NS-2078Opens with ⌘K / Ctrl+K. A search field, grouped actions and jump targets with a flush active row, a context tag for the current view date, and keyboard hints in the footer.
components/surfaces/CommandPalette.jsx · applied instance ui_kits/app/overlays.jsx
The reusable drawer shell — eyebrow + serif title + close, a scrollable body, and a footer for actions. It is chrome only: the body and footer are children. The applied event drawers (allocation, hire, role-change, contract-end) are instances that fill it; the drawer appends a ledger record, never a destructive edit.
components/surfaces/Drawer.jsx · applied instance ui_kits/app/overlays.jsx
Compact top bar: workspace mark, current workspace and date context, and icon-only global search and time-machine controls. White surface with a hairline bottom border.
Primary consultation navigation with three routes: Work groups, Functions, People. Flush wash only on the active route; touch targets stay at least 44px.
Current-state metrics and a drill-down list. Serif numerals in a hairline grid; each row carries a person-count chip and an allocation bar.
Replaces dense ledger tables on mobile: chronological cards with a kind dot, ledger copy, and a mono event reference.
Ledger entry visible at the selected global view date.
hash: b81244Marco Silva · effective 31 Jul 2026.
ev: 0c91d2Full-height sheet for a person, team, or event: sticky title, current allocation, and the ledger row tied to the view date.
Detail
Ledger entry visible at the selected global view date.
hash: b81244Pending manager decisions as stacked rows, not a data table. Mobile prepares a ledger event; the full edit stays on desktop.
One guided action flow: a single primary action, an explicit impact preview, and the ledger-event preview before confirmation.
Action
Impact preview
Capacity re-allocated · effective 01 Jul 2026.
draft: ev_pending_capacityExplains the result before confirmation: before/after totals, affected people, and the effective date.
Impact preview
Full-screen search and jump targets: a search field first, then grouped results for work groups, functions, people, and events. Keyboard hints stay hidden on touch.
A dark bottom-sheet time machine opened from the topbar; the selected date applies across Work groups, Functions, and People.
Time machine
Run from the package root. They guard package shape, component-family coverage, mobile information architecture, and brand-asset parity.
node tests/design-system-contract.test.jsnode tests/m2-component-families.test.jsnode tests/mobile-contract.test.jsnode tests/app-icon.test.js