For agents
The condensed brief an AI agent reads before designing or building any Oration screen.
What Canon is#
Read this page, then the digests. Together they are enough to build an on-system screen without further correction.
Canon is the design system behind the Oration suite: the CRM, the Agents Platform, the Contact Center and Ticketing, in one Next.js app. Components live in packages/canon and are imported as @oration/canon/…. The look is "The Well-Kept Ledger": graphite ink on a white plane, hairline rules, tabular figures and one quiet indigo for the decision.
The data is synthetic. The sample tenant is Cedarline, an accounts-payable automation vendor, and today is Monday, September 28, 2026. DESIGN.md at the repo root is the frozen spec; where code and spec disagree, the spec wins.
Always#
Fourteen habits that cover most of the system.
- 1Read llms.txt and the named rules before writing code.
- 2Import from
@oration/canon/components/<file>,hooks/<file>andlib/<file>. Never fork or restyle a component inside an app. - 3Style with semantic tokens (
bg-card,text-muted-foreground,border-border,bg-ink-65), never raw palette colors. - 4Give each view one filled button, the action it exists for. Everything else is outline, secondary, ghost or link.
- 5Set dense UI in
text-13, reading text intext-smand meta intext-xs. Every number getstabular-nums. - 6Raise surfaces with
Cardfrom@oration/canon/components/card(rounded-xl bg-card shadow-border, padding fromclassName, the element fromrender), never by hand. Split their insides withrounded-[10px] bg-muted/70wells. - 7Wrap every data component in
DataStatewithuseMockQuery, so it owns its skeleton, empty and error states. - 8Every skeleton you render reaches the screen through
DataStateorSkeletonReveal(@oration/canon/components/skeleton-reveal), neverloading ? <Skeleton /> : content. Before finishing, search your diff forSkeletonand check each one. See The Skeleton Reveal Rule. - 9Pair every status color with a text label, through
StatusLabelorTag. - 10Write sentence case copy in Cedarline's world: suppliers, invoices, remittances, payment runs, W-9s.
- 11Confirm every mutation with
toast.add, and offer Undo when the action is reversible. - 12Label every control. Icon-only buttons get an
aria-labeland a tooltip. - 13Animate with
springandexitfrom@oration/canon/lib/springs, then check the screen in dark and at 390px. - 14Never snap a label or icon that changes on screen. Wrap short label ternaries in
TextSwap, icon ternaries inIconSwap(orIconMorphfor menu, close, plus, minus, check, chevrons and arrows), and changing figures inAnimatedNumber. See Labels and icons that change.
Never#
The named rules say where each limit sits. The refusals are patterns that are never right here.
Named rules#
- The Quiet Indigo Rule
- Indigo is spent on the view's one filled primary action, on selection, on focus, and on live state that carries a text label (running, active, upcoming), plus the viewer's own markers: the unread dot, an @mention of you, the unsaved-changes dot. It never fills data, tints an icon tile, colors a heading or decorates.
- The One Filled Button Rule
- Each view has at most one filled indigo button, the action the view exists for. Everything else is outline, secondary, ghost, link or a destructive tint. In a stack of proposals, only the expanded one shows its filled button.
- The Ink Fill Rule
- Magnitudes are drawn in ink steps (80, 65, 45, 25, 15) on a Well Gray track: meters, progress, the stage track, ICP bars and the forecast bar. A fill turns semantic only when the value is itself a verdict, such as a strong ICP fit in Ledger Green. It is never indigo.
- The Option Hue Rule
- The ten categorical hues belong to select-option values (stage, lifecycle, tier, list and attribute options) and to identity tints on avatars, monogram tiles and the four app marks. An app hue appears in two places: the glyph tile, and the 3px pill beside the active sidebar item. Categorical hues never color-code sections, navigation text, status or charts.
- The Hairline-and-Lift Rule
- A raised surface takes its edge and its lift from one composite shadow (
shadow-border), never from a CSS border plus a shadow. CSS borders are for structural dividers only: table rules, header bottoms, section splits. - The Tint Well Rule
- Inside a card, a sub-region is a Well Gray tint at 70% with a 10px radius, never a second bordered or shadowed card. No card is ever nested in a card.
- The Scroll Edge Rule
- Clipped content shows its edge. Horizontal tab and view rows fade the clipped side over 2rem, and the last pinned grid column casts its edge shadow only once the grid scrolls sideways.
- The Thirteen-Fourteen Rule
- Dense UI is 13px, reading text is 14px, meta is 12px. 11px is for footnotes only, and nothing is set smaller.
- The Tabular Figures Rule
- Every number that changes or gets compared (amounts, counts, percentages, times, credits) is set in tabular figures, and numeric columns align right.
- The Machine Mono Rule
- Geist Mono is only for strings a machine produced or will parse: record and run IDs, API keys, tokens, codes, DNS records, template variables, E.164 phone numbers, timecodes. Figures, labels, keyboard keys and headings stay in Geist Sans.
- The Label-Beside-Color Rule
- Status is never color alone. A dot, tint or ring always travels with a text label (On track, At risk, Running, Passed), so the state reads in grayscale.
- The Sentence Case Rule
- Everything is sentence case. No eyebrow or kicker label above a heading, no uppercase labels, no letter-spaced captions.
- The Layout Over Punctuation Rule
- Metadata is never joined into dot-separated strings (A · B · C). Separate it with layout, commas or words.
- The Owned States Rule
- Every component that shows data owns its loading, empty and error states, with a skeleton shaped like its final layout. A whole page is never gated on loading.
- The Three Springs Rule
- Motion uses
spring.fast,spring.moderateandspring.slow, with exits one tier faster that never bounce. No other timings exist except the skeleton reveal, shimmer, the voice orb and meter fills, and keyboard-driven surfaces don't animate. - The Skeleton Reveal Rule
- Every skeleton that stands in for loading content reaches the screen through
DataState(data from a query) orSkeletonReveal(any other load), which pulse it, then cross-fade and cross-blur to the content over 400ms. Neverloading ? <Skeleton /> : content, anAnimatePresencefade or aFadeSwapfor a load. Suspense fallbacks, static skeleton galleries,AILoaderand button spinners are not reveals. - The Swap-In-Place Rule
- A label, icon or number that changes while people watch changes in place, never with a bare conditional. Short labels go through
TextSwap, two different icons throughIconSwap, two states of one line icon throughIconMorph, and figures throughAnimatedNumber. Text decided once (dialog titles, plurals, empty states), sentences, tooltips, menu items andtext-shimmerlabels stay plain.
Refused outright#
- Hero-metric card grids (big number, tiny label, trend chip). Show metrics as stat strips, rows, tables or real charts.
- Gradient text, glass, backdrop-blur decoration and thick colored left borders. The only gradient-clipped text is the shimmer on an in-progress label; the only animated gradient is the voice orb.
- Nested cards. A sub-region inside a card is a tint well.
- Solid red destructive buttons. Destructive is a 10% red tint with red text.
- Bare lucide icons in circles as empty states. Empty states use an Illustration, a title, one sentence and the next action.
- Guided tours, coach marks, modal walkthroughs and confetti.
- Emoji in UI, invented customer logos, testimonials, revenue metrics and compliance claims.
- Animating keyboard-driven surfaces such as the command palette or J and K moves, and entering from scale 0.
Decision shortcuts#
The component for the job. Each page says when not to use it and what to use instead.
| Job | Use |
|---|---|
| The action the view exists for | Button |
| A destructive action that needs a second look | Confirm dialog |
| The status of a record, run or call | Status label |
| A select-option value such as stage or tier | Tag |
| Metrics for a page or record | Stat strip |
| A magnitude against a target | Meter |
| A list of records people sort, filter and select | Data grid |
| A small read-only table inside a card | Table |
| Record details that keep the list in view | Sheet |
| A short, focused task | Dialog or Form dialog |
| A page of settings | Settings section or Save bar |
| Loading, empty and error for any data | Data state |
| Explaining a term or metric | Info tip |
| Introducing an overview page | Page intro |
| Feedback after an action | Toast |
| Two to five options, picked inline | Segmented control |
| One value from a long list | Combobox or Select |
| Switching between views of one thing | Tabs |
| Jumping anywhere by keyboard | Command menu |
| Drafting a free-text field with AI | AI generate button |
| An agent's proposed action that needs a decision | Approval card |
| Copying an ID, key or URL | Copy row |
| A button or status label whose words change | Text swap |
| An icon that changes with state | Icon swap or Icon morph |
| A sub-region inside a card | Well |
Screen recipe#
Walk this list before calling a screen done.
- ShellRender inside the
(app)shell. Don't build a sidebar or app header of your own. See Layout. - HeaderThe 48px sticky header with breadcrumbs and page actions on the right. Detail pages give the last crumb a switcher of siblings.
- Page shapeA document page (a 72rem column, 48rem for forms) or a full-bleed tool such as a list page.
- One filled buttonThe action the view exists for. Count them before you finish.
- StatesEvery data component has a layout-shaped skeleton, an empty state with an illustration and the next action, and an error with Retry. Every skeleton is inside
DataStateorSkeletonReveal, so it leaves through the skeleton reveal. - CopySentence case, specific to Cedarline, no eyebrows, no dot-joined metadata. See Writing.
- AccessibilityLabels, visible focus, keyboard parity and targets of at least 24px. See Accessibility.
- Dark and narrowCheck the dark theme and a 390px width. See Theming and Responsive.
Plain-text digests#
Both are generated from the registry, the rules and each component's guidance, so they say exactly what these pages say.
| Route | What's in it | Load it |
|---|---|---|
| /design/llms.txt | The brief, every named rule and refusal, and an index of every page with its one-line description. 293 lines today. | First, every time. |
| /design/llms-full.txt | Every page, then every component's guidance: when to use and not, rules, states, accessibility, props and known gaps. | When you know which components you need. |
Prompt to load Canon#
Paste this at the start of an agent session.
You are building UI for Oration, a four-app suite (CRM, Agents Platform, Contact Center and Ticketing) in one Next.js 16 app. Its design system is Canon, documented in the Handbook app at /design (http://localhost:3001 in development).Before writing code:1. Read /design/llms.txt in full. It holds the brief, every named rule and every refused pattern.2. For each component you plan to use, read its section in /design/llms-full.txt: when to use it, rules, states, accessibility, props and known gaps.3. Read DESIGN.md at the repo root. Where the code and DESIGN.md disagree, follow DESIGN.md.While building:- Import from @oration/canon/components/<file>, @oration/canon/hooks/<file> and @oration/canon/lib/<file>. Never fork a component into the app.- Use semantic tokens only. One filled button per view. Status always carries a text label.- Every data component renders loading, empty and error through DataState and useMockQuery.- Every skeleton becomes content through the skeleton reveal: DataState for query data, SkeletonReveal (@oration/canon/components/skeleton-reveal) for any other load. Never a bare loading ? <Skeleton /> : content conditional or a one-off fade.- Labels and icons that change on screen swap in place: TextSwap for short labels, IconSwap or IconMorph for icons, AnimatedNumber for figures. Never a bare conditional.- Copy is sentence case and specific to Cedarline, an accounts-payable automation vendor. Today is Monday, September 28, 2026.- Never build hero-metric grids, nested cards, gradient text, solid red buttons, emoji, tours or dot-joined metadata.Before you finish, walk the screen recipe at /design/for-agents#recipe, in light and dark, at 390 and 1440px wide.The digests are served by the running app. If the agent can't fetch URLs, point it at apps/handbook/src/components/design/registry/rules.ts and the component's guidance.ts in the repo instead.