Talaria Design System v2.1 Draft · 11 Oct 2026 · Made in Aotearoa New Zealand
tokens.json Implementation report
Talaria Design System · v2.1

Calm by default. Unmistakable when it matters.

The v2 system keeps what people recognise in Talaria, near-black and white neutrals, the #0070F3 blue, Inter and JetBrains Mono. It fixes the parts that slowed triage down: colours that disagreed with each other, a flat type scale, missing interaction states, unlabelled navigation and patterns that changed from page to page. v2.1 makes the whole system responsive, from a 390 phone to a 1440 desk, and polishes the details. It's written for developers and for the coding agents that work beside them.

1red, one grey ramp
13text styles, 12px minimum
AAevery text token, both themes
6interaction states per control
11ThemeExtensions, all lerpable
4size classes, 390 to 1440
One coherent redDanger, error and down share one ramp. Fatal moves to its own solid magenta.
Warning keeps its hueAmber in both themes, never orange, and well clear of red.
Real hierarchyDisplay 32, Headline 24, Heading 18, Title 15, each with its own job.
Every state, tokenisedHover, pressed, focus ring, disabled, error and a loading state that keeps its width.
Colour means good or badDeltas take their tone from the metric's polarity, and empty baselines show "—".
Labelled navigationA unique icon per destination, labels when expanded, tooltips when collapsed.
One of each patternA single page header, back affordance, filter bar, save bar and danger zone.
Triage firstThe Issues list starts above the fold, with insights folded into a one-line strip.
01 · v2.1

What's new in v2.1

Same visual language, now at every size, with a polish pass over the details that separate good from great.

Responsive by design

Four size classes (compact, medium, expanded, large) drive navigation, grid, type, density and every component. All mockups below are live layouts you can switch between Mobile, Tablet, Desktop and Wide.

Adaptive shell

Sidebar 240 / 56 on desktop, a labelled 72 rail on tablet, a bottom bar with More on phones. Env and project switchers become bottom sheets.

Touch-first below 900

44–48 px targets, 16 px input text, pressed states instead of hover, safe-area insets on bars, sheets and the save bar.

Polish pass

Optical icon strokes, a strict 4 pt rhythm (no more 14 px paddings), quieter dark shadows with a top highlight, HTML chart axes at 12 px, new empty-state art and a micro-interaction spec.

Tokens and Flutter

breakpoints, responsive, adaptiveType, touch and safeArea in tokens.json; TalariaBreakpoints, TalariaLayout, context.sizeClass and TalariaAdaptiveShell in Dart.

Still AA

Every text token keeps ≥ 4.5:1 in both themes, and touch targets now meet WCAG 2.2 target size (2.5.8) with room to spare.

02 · Principles

Six rules we design by

When a decision is unclear, these settle it, in this order.

01

Triage comes first

Show the list of problems before any summary. Charts and insights support the list and never push it below the fold.

Issues list rows start at y ≤ 220px on a 900px viewport.
02

Colour carries meaning

Each hue family has one job. Blue is interaction and info, red is bad, amber is caution, green is good, magenta is fatal, iris is AI and teal is production. Decoration never borrows a status colour.

A delta is red only when the change is bad for the user.
03

Quiet until it matters

Use neutral surfaces, hairline borders and no gradients except for AI. Each row gets at most one tinted badge, and every other attribute stays quiet text.

Severity becomes a 3px edge bar, and status becomes an icon.
04

Legible at a glance

Text is at least 12px. Every text token passes WCAG AA on every surface in both themes. Numbers use tabular figures, and nothing relies on colour alone.

fg.subtle is 5.11:1 on the light canvas and 5.73:1 on dark.
05

One way to do a thing

There's one page header, one back affordance, one filter bar, one save pattern and one empty state. A new page is built from these parts.

Filters always serialise to the URL.
06

Built for you and your agent

Every token is machine-readable, every state can be deep-linked, and every widget renders on its own. An agent should be able to read the UI as easily as you do.

/_dev/style-guide/buttons?theme=dark&state=focus
03 · Old vs new

The fixes that matter most

Each pair puts the current production style (left, using the literal old values) beside the v2 replacement.

Three reds become one ramp plus fatal
● Old, three unrelated reds
#E5484D#F87171#DC2626#FF4D6D#BE123C
● New, one red and a separate fatal hue
red.500 #E5484D (the brand red) anchors one ramp, and fatal moves to magenta.600 #C0267A.
Before, danger, error and down used three different reds. Fatal was a fourth red that was hard to tell from error, especially in dark.
Warning keeps its hue across themes
● Old
dark #F5A623light #ED5F00
WARNINGERRORlight: too close
● New
amber.500 #F5A623 both
Warning Error 38° vs 358° hue
Light warning was orange (#ED5F00 and #C2410C), which sat about 20° from danger. v2 uses amber with dark-amber text (#9A5B00, 5.2:1).
Error and fatal are clearly distinct
● Old
ERRORFATAL
● New
Error Fatal tinted + icon vs solid + bolt
Fatal uses a different hue, a different fill (solid) and a different glyph, so it survives every colour-vision deficiency and greyscale.
Type scale with real steps
● Old 24 / 20 / 16, all 600
Display
Heading
Title
● New 32 / 24 / 18 / 15
Display
Headline
Heading
Title
The scale goes from a fixed 4px step to a 1.3 ratio, tightens tracking as size grows, and gives each step one job (page title, section, card).
Badge casing has one rule
● Old (per-call uppercase flag)
RESOLVEDERRORProductionEnabled
● New
Resolved Error production Enabled
Semantic labels use Sentence case at 12/500. Literal values such as env names, tags and releases use mono, exactly as stored. Nothing is uppercased, and "Enabled" is a badge rather than green text.
Loading keeps width and label
● Old
Save
The button collapses and the label disappears.
● New
Save changesSaving…
The width is locked, the spinner replaces the leading icon and the label changes to a verb.
Deltas are coloured by meaning
● Old, same data, opposite colours
Events
111 +909% vs prior wi…
Errors
111 +909% vs prior wi…
● New
Events
111 909%
from 11 · volume is neutral
Errors
111 909%
from 11 · lower is better
Each metric declares a polarity (higherIsBetter, lowerIsBetter or neutral). The arrow always shows direction, and the colour shows whether the change is good or bad. Baselines under 20 show the absolute "from 11" instead of a misleading percentage.
Environments are coloured and shaped
● Old (untoned)
ProductionStagingDevelopment
● New
production staging development test
Production is teal with a solid dot and staging is plum with a half dot. Development and test stay neutral, with a hollow dot and a dashed diamond. The glyph shapes mean you can tell them apart without colour.
04 · Colour

Colour

Primitive ramps are raw material, and components only ever read semantic or tone tokens. Neutrals are one ramp, the Tailwind-neutral greys the brand already uses, with three extra steps (450, 550, 875) added to make AA text possible. Click any swatch to copy its hex.

Primitive ramps tokens.json → primitive.color

Semantic tokens both themes · contrast against canvas / surface / muted

Ratios marked AAA are ≥ 7:1 and AA are ≥ 4.5:1. fg.disabled is intentionally below AA (disabled controls are exempt) but stays ≥ 2.2:1. Success fills never carry text.

Tone matrices bg / fg / border, rendered live

Colour rules

  • Blue is for interaction (primary, links, selection, focus) and the info severity. It's never a status of "good".
  • Red covers danger, error, down, regressed and bad deltas. It's a single ramp, with red.500 as the brand red.
  • Amber covers warning and degraded. Text on an amber fill is always dark.
  • Magenta is reserved for fatal and only fatal.
  • Iris marks AI. It always appears with the sparkle glyph, and it's the only place a gradient is allowed.
  • Teal and plum are environment identity (production and staging), shown as a dot plus a mono label.
  • Tinted tones use soft bg, fg text and a border. Solid fills are only used for fatal, primary, danger and AI.
  • Custom seed themes remap neutrals and accent only. Status, severity and environment tones stay fixed, so meaning never drifts.
05 · Typography

Type scale

Inter for UI, JetBrains Mono for anything a machine wrote, such as frames, IDs, tags and env values. The scale follows roughly a 1.3 ratio from 15 to 32, with tracking tightening as size grows. Every number on screen uses tnum.

One headline per page. The page title uses headline. Sections use heading. Cards, dialogs and rows use title. display is only for empty and 404 states, onboarding and prices.
12px is the floor. The old 11px badge text becomes captionStrong at 12/16. Captions must use fg.subtle or stronger. Never place a caption on a tinted fill without checking the tone's fg.
Mono for machine text. Error types and messages stay in Inter for scannability, while culprits, frames, fingerprints, tag values and env names use mono. Mono is never used for headings.
06 · Foundations

Space, radius, layout, elevation and motion

A 4pt grid with a half-step for icon nudges, five radii mapped to component size, three content widths chosen by page type, four elevation levels and six durations.

Spacing TalariaSpacing

Radius TalariaRadii

xs 4 · badge, kbd
sm 6 · button, input
md 8 · card, menu
lg 12 · dialog, panel
xl 16 · page frame
full · pill, avatar

Nested radius = outer − padding. A card (8) holding a 4px-inset item uses 4.

Content widths

narrow · 720 · settings forms, dialogs-as-pages
default · 1200 · lists, detail, overview
wide · 1440 · dashboards, performance
fluid · traces, replay, whiteboards (gutter only)

Gutters: 16 mobile · 24 tablet · 32 desktop. Each page type uses one width, and the header always aligns to the same column as its content.

Density row heights for tables and event or issue rows

Compact · 36
ErrorOne line only, title + count

Event streams, logs, tags, power users (toggle).

Default · 48
Tablessingle line + badges

Tables, settings lists, API keys.

Comfortable · 56–64
Issue rows: 2 lines maxculprit · meta

Issue list (the default). Never three lines, and never repeat the title as the message.

Elevation TalariaElevation · shadows deepen and surfaces lighten in dark

e0 flatCards, tables, border only
e1Hovered card, sticky header
e2Menu, popover, select
e3Dialog, drawer, ⌘K
e4Toast

Motion TalariaMotion · hover a row to play

Micro-interactions tokens.json → interactions

ElementTriggerChangeDurationEasing
Button, icon button, nav item, rowhover (pointer only)state layer 0 → 5%/6%fast 100standard
Button, icon button, nav item, rowpressstate layer → 9%/10%; primary fill → accent.pressed; translateY 0.5pxfast 100 in / base 160 outstandard
Any focusablekeyboard focus2px ring + 2px gap, appears instantly; never animates layoutinstant 0—
Checkbox, switch, segmented thumb, tab indicatortoggle / selectfill, thumb position, indicator x/widthbase 160standard
Menu, popover, selectopen / closeopacity 0→1, scale .98→1, y −4→0 / reversemoderate 240 / fast 100enter / exit
Dialog, side draweropen / closescrim fade; panel y 8→0 (dialog) or x 24→0 (drawer)slow 320 / moderate 240enter / exit
Bottom sheetopen / drag / dismissy 100%→0; follows finger; flings past 30% or 800px/s dismissslow 320 / moderate 240enter / exit
Toastenter / exity 16→0 + fade / fademoderate 240 / fast 100enter / exit
Skeletonloadingshimmer sweep; appears after 150ms, stays ≥300ms1200 cyclelinear
Chartfirst paint / range changebars grow from baseline / cross-fadedeliberate 480 / base 160standard
Focus trail rowreturn from detail2px accent edge fades outslow 320 after 2sexit
Sidebarcollapse / expandwidth 240↔56, labels fade at 40%moderate 240standard

Everything honours reduced motion: durations drop to 0 and only opacity changes remain.

Under prefers-reduced-motion (or MediaQuery.disableAnimations), positional motion becomes a 100ms cross-fade, the shimmer stops, and spinners keep turning at half speed so progress is still visible.

07 · Iconography

Icons and the navigation icon map

Outlined, 1.75 stroke on a 24 grid, rendered at 16 (inline), 18 (collapsed nav) or 20 (empty states). In Flutter, keep AppIcons as the only source and use the rounded outlined Material set. The glyphs here are drawn to match. Each destination gets exactly one icon, and no icon is reused for two destinations.

Utility set (subset of AppIcons)
Rules. The bell is the notification inbox in the top bar only. Alerts use the ringing bell and Monitors use a pulse. Tag is reserved for tags. Sparkle is reserved for AI. An icon-only button needs a tooltip and a semantic label, both naming the action ("Copy event ID", not "Copy").
Motif. The logo's fanned blades appear in only three places: the logo, the AI "thinking" indicator (blades light up in sequence) and the empty-state art. They're never used as decoration elsewhere.
08 · Responsive

Responsive system

Layout is chosen by window width, not device. Four size classes map onto Material 3 window size classes: compact is touch-first with a bottom bar, medium keeps a labelled rail, expanded and large get the full sidebar and every column.

Compact mobile0–599 px · preview 390M3: Compact (<600)
Medium tablet600–899 px · preview 820M3: Medium (600-839) + low end of Expanded
Expanded desktop900–1439 px · preview 1280M3: Expanded (840-1199) + Large (1200-1599)
Large wide≥ 1440 px · preview 1440M3: Large / Extra-large (>=1200 / >=1600)

Layout per size class tokens.json → breakpoints, responsive

Token · tokens.json → responsive Compact Medium Expanded Large
Columns · gutter · margin4 · 16 · 168 · 16 · 2412 · 24 · 3212 · 24 · 40
NavigationBottom bar 64 (4 + More) · modal drawerRail 72, labelled · modal drawerSidebar 240, collapses to 56 with tooltipsSidebar 240, collapses to 56 with tooltips
Top bar56 px · back, title, env, search56 px · crumbs, env, ⌘K48 px · crumbs, env, ⌘K48 px · crumbs, env, ⌘K
Content widthfluidfluidmax standard 1200max wide 1440
Input · densitytouch · comfortabletouch · comfortablepointer · standardpointer · standard
TablesStacked cardsFewer columns (no sparkline)All columnsAll columns
FiltersSearch + Filters (n) → sheetInline search + Filters (n) → sheetInline chipsInline chips
Detail railStatus + assignee, rest collapsedCard above content, 2 columnsSide, 300Side, 340
Dialogsfullscreen | bottomSheetcentered 560 | bottomSheet (pickers)centered 440-560centered 440-560
Drawersfullscreenside 400 (>=720) | fullscreenside 400-480side 480
PaginationLoad moreLoad moreNumbered pagesNumbered pages
Header actions shown1 + overflow menu2 + overflow menu3 + overflow menu3 + overflow menu
Chart ticks (x / y)3 / 3 · swipe5 / 46 / 58 / 5

Grid 4 · 8 · 12 columns

Compact · 390
4 columns · 16 gutter · 16 margin
Medium · 820
8 columns · 16 gutter · 24 margin
Expanded · 1280
12 columns · 24 gutter · 32 margin

Adaptive type tokens.json → adaptiveType

StyleCompactMediumExpandedLarge
display
Page not found
28/36
Page not found
30/38
Page not found
32/40
Page not found
36/44
headline
Issues
22/28
Issues
24/32
Issues
24/32
Issues
24/32
heading
Needs attention
17/24
Needs attention
18/26
Needs attention
18/26
Needs attention
18/26
metric
1,284
24/28
1,284
26/32
1,284
28/32
1,284
28/32

Title, Body, Label and Caption never change, so dense data reads the same on every device. In Flutter: context.tTextAdaptive or TalariaTextStyles.forSizeClass(c).

Touch, pointer and safe areas tokens.json → touch, safeArea

Control heights

smPointer 28
mdPointer 32
lgPointer 40
smTouch 36
mdTouch 44
lgTouch 48
Minimum target
48 × 48 (44 on iOS)
Row height
pointer 48 · touch 56 · card 72
Nav item
sidebar 32 · rail 56 · bottom bar 64
Input text
16 px on touch
Safe area
Pad top app bar by the top inset, bottom bar / sheets / save bar / AI composer by the bottom inset; landscape phones also pad left/right.

Pointer vs touch

  • Hover styles only apply under (hover: hover) and (pointer: fine). On touch, the pressed state layer (10%) is the only feedback.
  • Never hide actions behind hover on touch: show an overflow button or support long-press and swipe.
  • Tooltips need a long-press equivalent; icon-only buttons keep a visible label in the bottom bar and rail.
  • Inputs use 16px text on touch so iOS Safari never zooms on focus (web build).
  • Hit area is at least 48x48 (44x44 on iOS) even when the visual control is smaller; adjacent targets keep 8px between hit areas.

Components at each breakpoint live container queries, true pixel widths

Each box is a real container at the stated width, so what you see is exactly what the layout rules produce.

Filter bar

The bar never wraps into three lines. Below 900 the chips collapse into one Filters button with a count.

  • ExpandedSearch, status segments, chips, time range, sort
  • MediumSearch + status + Filters (n) sheet
  • CompactSearch + Filters (n); status scrolls below
Compact 390
LevelError, FatalFilter
Medium 820
LevelError, FatalFilter
Expanded 1280
LevelError, FatalFilter

Issue list → cards

One markup, three layouts. Counts keep their labels when columns disappear.

  • ExpandedAll columns, sparkline, pages
  • MediumSparkline column hidden, 64 rows, load more
  • CompactTwo-line cards: title, message, meta wraps; events on the right
Compact 390
IssueLast 24hEventsUsers
TimeoutErrorupstream request timed out after 30000ms
Fatalapi/checkout.dart in submitOrderproductionRegressed 9 min ago
412events88AC
NullPointerExceptionNull check operator used on a null value
auth/session_store.dart in refreshproduction1.4.22 d old
1,284events312
StateErrorBad state: No ProviderScope found
main.dart in runAppstagingNew1 h old
96events14HD
ApiValidationExceptionThat code does not match. Try again.
auth/email_idp.dart in verifyproduction5 d old
61events40
1–25 of 41
Showing 25 of 41
Medium 820
IssueLast 24hEventsUsers
TimeoutErrorupstream request timed out after 30000ms
Fatalapi/checkout.dart in submitOrderproductionRegressed 9 min ago
412events88AC
NullPointerExceptionNull check operator used on a null value
auth/session_store.dart in refreshproduction1.4.22 d old
1,284events312
StateErrorBad state: No ProviderScope found
main.dart in runAppstagingNew1 h old
96events14HD
ApiValidationExceptionThat code does not match. Try again.
auth/email_idp.dart in verifyproduction5 d old
61events40
1–25 of 41
Showing 25 of 41
Expanded 1280
IssueLast 24hEventsUsers
TimeoutErrorupstream request timed out after 30000ms
Fatalapi/checkout.dart in submitOrderproductionRegressed 9 min ago
412events88AC
NullPointerExceptionNull check operator used on a null value
auth/session_store.dart in refreshproduction1.4.22 d old
1,284events312
StateErrorBad state: No ProviderScope found
main.dart in runAppstagingNew1 h old
96events14HD
ApiValidationExceptionThat code does not match. Try again.
auth/email_idp.dart in verifyproduction5 d old
61events40
1–25 of 41
Showing 25 of 41

Page header and tabs

Primary action stays visible; the rest overflow into ⋯. Tabs scroll with an edge fade instead of wrapping.

  • ExpandedInvestigate, Resolve, ⋯
  • MediumSame, culprit hidden
  • CompactBack lives in the app bar; Resolve fills the row; AI becomes an icon
Compact 390
Issues

TimeoutError

upstream request timed out after 30000ms
FatalRegressedNORTH-4F2api/checkout.dart in submitOrder
Medium 820
Issues

TimeoutError

upstream request timed out after 30000ms
FatalRegressedNORTH-4F2api/checkout.dart in submitOrder
Expanded 1280
Issues

TimeoutError

upstream request timed out after 30000ms
FatalRegressedNORTH-4F2api/checkout.dart in submitOrder

Detail rail

The rail is a side column on desktop, a two-column card on tablet and a short summary on phones.

  • ExpandedSide rail 300 (340 wide)
  • MediumCard above the stack trace
  • CompactStatus + assignee, then More details
Compact 390
Medium 820
Expanded 1280

Charts

Axis text stays 12 px at every width; compact shows 3 ticks and scrolls with the newest day pinned right.

  • Expanded6 x-ticks, tooltip on hover
  • Medium5 ticks, tap for tooltip
  • Compact3 ticks, swipe for history
Compact 390

Events by severity

dashed line = prior period
Swipe for earlier days
Medium 820

Events by severity

dashed line = prior period
Swipe for earlier days
Expanded 1280

Events by severity

dashed line = prior period
Swipe for earlier days

Billing plans

Plans sit side by side from 900 and stack below, with the recommended plan's primary button always last in reading order.

  • Expanded3 columns
  • MediumStacked, full width
  • CompactStacked, full width
Compact 390
Developer
$5/ month
  • 5k events
  • 1 project
Team Current plan
$29/ month
  • 50k events, 500 replays
  • 5 projects
Business Recommended
$89/ month
  • 500k events, 5k replays
  • SSO, audit log
Medium 820
Developer
$5/ month
  • 5k events
  • 1 project
Team Current plan
$29/ month
  • 50k events, 500 replays
  • 5 projects
Business Recommended
$89/ month
  • 500k events, 5k replays
  • SSO, audit log
Expanded 1280
Developer
$5/ month
  • 5k events
  • 1 project
Team Current plan
$29/ month
  • 50k events, 500 replays
  • 5 projects
Business Recommended
$89/ month
  • 500k events, 5k replays
  • SSO, audit log

Confirm dialog → bottom sheet

Same content and button order. On phones the primary action is on top, within thumb reach.

  • ExpandedCentred 440 dialog
  • MediumCentred dialog
  • CompactBottom sheet, safe-area padded
Compact 390
Medium 820
Expanded 1280

Toast

Bottom-centre on desktop and tablet; on phones it spans the width just above the bottom bar.

  • ExpandedBottom-centre, min 320
  • MediumBottom-centre
  • Compact16 inset, 12 above the nav
Compact 390
Medium 820
3 issues resolvedUndo
Expanded 1280
3 issues resolvedUndo

AI drawer

A 420 side panel from 720 up, full-screen below so the answer and composer get the whole phone.

  • ExpandedSide panel 420 over a scrim
  • MediumSide panel 420
  • CompactFull screen, composer above the home indicator
Compact 390
Medium 820
Expanded 1280
09 · Responsive overview

One page, four sizes

Switch pages to compare. Nothing here is a separate design: each frame is the same component tree at a different width.

Same markup, four container widths
Mobile · 390Compact · bottom bar · cards
Tablet · 820Medium · rail 72 · reduced columns
Desktop · 1280Expanded · sidebar 240 · all columns
Wide · 1440Large · 40 margin · wider rail
10 · Buttons

Buttons and icon buttons

Five variants, three sizes and six states. Hover and pressed are state layers (fg at 5% or 9%) over the resting fill, so they work on any surface and in custom seed themes. Focus is a 2px ring with a 2px canvas gap that never moves the layout. Loading locks the width and keeps the label.

DefaultHoverPressedFocusDisabledLoading
PrimaryResolveResolveResolveResolveResolving…
SecondaryAssignAssignAssignAssignAssigning…
GhostView traceView traceView traceView traceLoading…
DangerDeleteDeleteDeleteDeleteDeleting…
Danger quietRevokeRevokeRevokeRevokeRevoking…
AIInvestigateInvestigateInvestigateInvestigateInvestigating…
Sizes · 28 / 32 / 40

sm for tables, toolbars and dense rows, md as the default, lg for empty states and onboarding. The hit target is always ≥ 44px using an invisible extension.

Composites

A split button handles "Resolve in next release". Overflow goes to "More" and keeps the same order as the visible actions.

Icon buttons · default, hover, pressed, selected, focus, disabled

The visual size is 32 (24 for sm) and the hit target is 44. Idle uses fg.subtle and hover uses fg.default. A tooltip on hover and focus is mandatory.

Do

One primary per view region.

Don't

Competing fills. Archive isn't destructive.

Ghost affordance. Ghost buttons always have a leading icon or sit in a toolbar group, so they never read as plain text. Destructive actions in a toolbar use danger quiet. The solid danger fill is reserved for confirmation dialogs.
11 · Inputs

Text inputs, number inputs and select

Filled with bg.muted and bordered with border.strong. Focus switches to the surface fill with a 1px focus border and a 3px soft ring. Labels sit above, and helper or error text sits below. Error text always comes with an icon, never colour alone.

Search issues…
Searches title, culprit and tags
Search issues…
 
TypeError
 
TypeError
 
checkout-api.
Slugs can't end with a full stop
checkout-api.
Slugs can't end with a full stop
Managed by your organisation
Ask an owner to change this
tal_live_8f2c…a91e
 
Number input · AppNumberInput
× 100%
Between 0 and 1. Sanitised as you type.
events / 5 min
Select · closed / open
production
Date range field
Last 7 days4 Oct → 11 Oct

A preset writes range=7d to the URL, never "time:custom".

12 · Selection

Checkbox, radio, toggle, segmented control and tabs

Selected states use the accent fill, and hover adds a 4px state-layer halo. Toggles take effect immediately. A form's checkboxes wait for Save. Status is never shown as green text such as "Enabled".

OffOnIndeterminateHoverFocusDisabledError
CheckboxUnhandledUnhandled3 of 8HoverFocusLockedRequired
RadioEmailSlack—HoverFocusDisabledPick one
ToggleLiveLive—HoverFocusDisabled—
Segmented control

Use 2–5 mutually exclusive options. The thumb animates at base 160ms. Never use it for navigation.

Tabs · default, hover, selected, focus, disabled, count

Tabs switch views of one object and are reflected in the URL (?tab=events). A 2px fg indicator, not accent, keeps the blue for links.

Toggle row (settings)
Error alerts
Email me when a new issue is created
Session replay
Capture 10% of sessions
PII scrubbing
Required on Business plans
13 · Badges & chips

Severity, status, environment and health

Every badge pairs a glyph with a label, so meaning never depends on colour. They're 20px high, use 12/500 text and Sentence case, with no uppercase. A row carries at most one tinted badge, and the others become quiet text or icons.

Severity · SeverityTones
Trace Debug Info Warning Error Fatal
Warning Error Fatallg · 24 for headers
Issue status · StatusTones
Open Regressed Resolved Ignored
OpenResolvedIgnoredquiet · in rows
Environment · EnvironmentTones (mono, value as stored)
production staging development test preview-42

Unknown envs fall back to the neutral tone with a hollow dot. Production is the only teal.

Health · cron and service state
Healthy Degraded Down Unknown Processing
LivePausedOffLive status pill
Tags, counts, kbd, mechanism
runtimedartosmacOS 15.0release1.4.2+31+13 more1,284⌘KUnhandledHandled
Casing & content rules
  • Semantic labels use Sentence case: "Resolved", "Third party", "Over limit".
  • Literal values (env, tag, release, slug) use mono, verbatim: production.
  • Never uppercase. The uppercase flag is removed from AppBadge.
  • Badges are not buttons. Interactive chips use the filter-chip pattern.
14 · Containers

Cards and tables

Cards are flat (e0) with a hairline border. Interactive cards gain border.strong and e1 on hover. A table row colours only its badges, never whole cells. Numbers align right and use tabular figures.

Latest release

Static card · title + caption
1.4.2+31
Crash-free
99.82%
Adoption
64%
New issues
2

checkout-api

Interactive · hover state shown
23 open issues128.4k events

Monitor · nightly-backup

Selected · accent ring
Ok
Schedule
Every day at 02:00 NZT
Last ok
7 h ago · 3m 12s

Table default density · sortable header · selected and focus rows

TitleStatusLevelEnvironmentEvents UsersLast seen
NullPointerException in AuthService.refreshOpenErrorproduction1,2843122 min ago
TimeoutError upstream request timed outRegressedFatalproduction412889 min ago
Unhandled rejection in payment webhookOpenWarningstaging5641 h ago
FormatException Unexpected character at 1ResolvedInfodevelopment313 d ago

Header uses captionStrong in fg.subtle and is sticky. Hover adds a state layer, selected uses state.selected, and keyboard focus draws a 2px inset bar. Never tint a whole cell.

15 · Rows

Issue rows and event rows

An issue row is two lines at most. The first line is the type and message. The second line is the culprit, environment, release and age. A 3px left edge carries severity, status is an icon, and volume is a sparkline plus tabular counts. An event row is a single 40px line.

IssueLast 24hEventsUsers
TimeoutErrorupstream request timed out after 30000ms
Fatalapi/checkout.dart in submitOrderproduction1.4.2 Regressed 9 min ago
412+38/h88AC
NullPointerExceptionNull check operator used on a null value
auth/session_store.dart in refreshproduction1.4.2First seen 2 d ago
1,284312
Unhandled rejectionin payment webhook handler
webhooks/stripe.ts:88stagingNew · 1 h ago
564HD
FlutterErrorRenderFlex overflowed by 24 pixels on the right
widgets/issue_row.dart in buildResolved in 1.4.1
41
SlowFrameframe took 48ms (compact density)
237
2 selected
Bulk bar · inverse surface, floats over the list bottom
Hover state.hover layer, and row actions appear on the right.
Selected state.selected plus a checked box.
Focus 2px ring. J/K move, X selects, E resolves, Enter opens.
Resolved / ignored title drops to fg.muted, a status icon leads, and the sparkline goes grey.

Event rows 40px · one line · timestamp, level, environment, message

14:02:31.204ErrorNull check operator used on a null value · session_store.dart · production · Chrome 1482 min
14:02:29.880WarningRetrying request (2/3) · api_client.dart · production · iOS 18.12 min
14:01:57.012FatalIsolate crashed: Out of memory · worker.dart · production · Android 153 min
14:01:40.551InfoUser signed in · auth.dart · staging3 min

Tags move to the detail view. In the old event row a single event took around 95px and three lines, repeating the title as the message.

16 · Filter bar

One filter bar for every list

Issues, Events, Replays, Traces and Releases share one component: a search field, a status segmented control (where it applies), token chips (key, operator, value) and "Add filter", with time range and saved views on the right. Every change writes to the URL immediately using replace, never push, so Back works in one press.

LevelError, Fatal Envproduction releaseis1.4.2 Filter
/projects/northline/issues?status=unresolved&level=error,fatal&env=production&release=1.4.2&q=TypeError&range=7d
Phone · ListFilterChrome
Search
Error, Fatalproduction7d
Empty result caused by a filter

No issues match these filters

3 filters are hiding 41 issues from the last 7 days.

Rules. The search box mirrors q after a reload. Defaults aren't written to the URL. Unknown values (for example env=BOGUS) show as a red-bordered chip saying "Unknown environment". Chip state and search text come from one source of truth.
19 · Feedback

Banners and toasts

Banners are persistent, inline and scoped to a page or card. Toasts are transient (5s, or 8s with an action), confirm something you did, and always offer Undo for reversible bulk actions. Errors that need action are never toasts.

3 issues resolvedUndo
Event ID copied
Couldn't save. You're offline.Retry
Exporting 1,284 events…42%
Placement. Toasts appear bottom-centre on desktop and tablet, and full-width above the bottom bar on phones (16px side insets, clear of the home indicator), with a maximum of three stacked. They use the inverse surface at e4 and enter with moderate 240ms using the enter easing. Screen readers hear them through a polite live region.
20 · States

Empty, error, 404 and skeletons

Every state names the cause and offers a real next step. Empty is not error, and error is not not-found. A 404 renders inside the app shell so navigation still works. Raw exceptions are never shown. A request ID sits behind "Details".

No replays yet

Session replay is off for this project. Turn it on to see what users did before an error.

Empty · first-run

Couldn't load issues

The Talaria API didn't respond in time. Your data is safe, and this is usually brief.

req_8f2c91 · 504 · Copy details

This issue doesn't exist

It may have been merged or deleted, or the link has a typo. No retry needed.

Not found · retry hidden when retry: false

404, in the shell

All environmentsSearch or jump to…⌘KHD
404 · /projects/northline/web-vitals
Page not found

There's no page at this address in Northline. Did you mean Performance › Vitals?

Skeletons shape matches the content · shimmer 1.2s · appear after 150ms · never mix spinners and skeletons in one region

  • Skeletons mirror the final layout box for box, so nothing shifts when data lands. The issue detail page no longer fills in piecemeal.
  • Show them only after a 150ms delay to avoid flashes, and keep them for at least 300ms once shown.
  • Refetching keeps the stale data in place with a 2px progress line at the top of the region, not a skeleton.
  • Buttons show a spinner and keep their label. Whole-page cold loads use the blade loader.
21 · Data display

Stat tiles, deltas and the chart palette

Every tile has the same anatomy: label, value, delta and comparison, with a fixed height so a row of tiles always lines up. Delta colour comes from polarity. An empty or tiny baseline shows "—" or an absolute change rather than an alarming percentage.

Errors
1,28418%
vs prior 7 days · lower is better
Crash-free sessions
99.82%0.3 pt
vs prior 7 days · higher is better
Events
48.2k12%
volume · neutral
Crash-free sessions
—
No sessions in this range
p95 latency
312ms24 ms
vs 1.4.1 · lower is better
New issues
85
from 3 · small baseline shows absolute change
Quota used
82%
41k of 50k events · warning from 80%, danger from 90%

Events by severity

Last 30 days · stacked · uses SeverityTones.chart
InfoWarningErrorFatalDebug

p95 latency by route

Categorical palette · series 1–3 · 2px lines · 16% area
/checkout/projects/auth/refresh

Chart palette ChartPalette · Okabe–Ito derived · re-tuned per theme

Order matters. Use series in order 1→8. Series 1–4 (blue, orange, teal, pink) stay pairwise distinguishable under protanopia, deuteranopia and tritanopia. Beyond five series, group the rest as "Other" in grey.
Dark mode. Bars and lines are full-strength colours (≥ 6:1 on surface), never pale tints, and the area fill is 16%. This fixes the muddy pink bars.
Axes. At most 5 or 6 ticks with no repeated dates, tabular numbers, a grid in chart.grid, and labels never truncated. Every chart has an aria summary and a "View as table" toggle.
22 · Billing

Plan cards and usage meters

The current plan has a strong border and a "Current plan" badge. The recommended plan has the accent ring and the only primary button. Others use secondary buttons, so the outline and primary styles no longer clash. Prices use display with tabular figures.

Developer
$5/ month
  • 5k events
  • 1 project
  • MCP for agents
Team Current plan
$29/ month
  • 50k events, 500 replays
  • 5 projects, unlimited seats
  • 100 AI investigations
Business Recommended
$89/ month
  • 500k events, 5k replays
  • SSO, audit log
  • PII scrubbing rules
Enterprise
Custom
  • Volume pricing
  • Data residency (NZ, AU)
  • Dedicated support

Usage this period

1 Oct → 1 Nov · 21 days left
$29.00 estimated
Events3.0k / 50k
Transactions72k / 100k
AI investigations86 / 100
Replays7 / 500
Data transfer1.02 GB / 1.00 GB
Analytics13k / 500k
23 · AI

AI surfaces

AI has its own tokens: ai.default, ai.fg, ai.soft, ai.border and a two-stop gradient. It's the only gradient in the product. AI output is always labelled, cites its sources and offers a way to verify it. Markdown uses Talaria tokens rather than Material #2196F3 and #BBDEFB.

Investigation Draft
Read 412 events and 3 replays
Compared releases 1.4.1 → 1.4.2
Checking the source for submitOrder…

Likely cause: SessionStore.refresh() returns before the new token is written, so submitOrder reads a null session for about 300ms after a refresh. This started in 1.4.2, which removed an await.

// session_store.dart:142 Future<void> refresh() async { final token = await _api.refresh('session'); _cache.write(token); // ← missing await }
412 events3 replayssession_store.dart
AI can be wrong. Check the cited lines.
Ask a follow-up…↵
Entry points

One AI button style everywhere, replacing the earlier one-off "Investigate with AI" and "Explain with AI" styles.

Thinking states
Reading events…blade loader, reduced motion → static
Agent activity (MCP)
Cursor agent added tracing to checkout.ts
via MCP · 4 min ago · PR #212
Running
You resolved NORTH-4F2
2 min ago
24 · Overlays

Dialogs, drawers and the focus trail

Dialogs are for decisions. Drawers are for context you look at alongside the page. The focus trail stacks drawers, so you can go from an issue to an event to a trace and back without losing the list underneath. All overlays trap focus, close on Esc, return focus to the element that opened them and sit at e3 over the scrim.

Issues
POST /api/checkout1.82 s
http.server
· auth.refresh
· db.query
· payments.charge
Dialog widths 440 confirm · 560 form · 720 complex. Use title Title 15, body 14 and footer actions on the right, with the primary last.
Destructive dialogs name the object and the consequence in numbers. Typing to confirm is only for irreversible deletes.
Drawer 400 (or 560 wide) on desktop, full-screen below 720. It slides in over moderate 240ms with the enter easing, and the trail breadcrumbs show depth.
Focus trail Back pops one level and X closes the whole trail. Each level has a URL (?focus=event:9c1e,trace:45bf) so the stack survives a reload.
25 · Page · Issues

Issues list, triage first

The first issue starts about 200px from the top of the page, not at 740px. Insights fold into a single strip that can be expanded, and the chart and "Top issues" move into it. Every filter lives in the URL. Switch the viewport to see it at 390, 820, 1280 and 1440.

Expanded · 12 columns · 32 margin · sidebar 240
Above the foldThe header, the filter bar, a one-line insight strip, then the rows. The old Filters, Insights, chart and Top issues stack is gone.
One tinted badgeSeverity shows as the left edge. Only fatal or warning gets an inline badge (as an exception to the default error level).
EnvironmentThe global switcher reads "All environments" by default or remembers your last choice. It no longer defaults to development.
KeyboardJ/K, X, E (resolve), I (ignore), A (assign) and / to search. Shortcuts are listed under ?.
26 · Page · Issue detail

Issue detail

One back link, one header and tabs for the views. The main column shows the exception with your own frames expanded and library frames folded. The 300px rail (340 at wide) holds state and facts. Skeletons match this layout, so it never fills in piecemeal. Switch the viewport to see it at 390, 820, 1280 and 1440.

Expanded · 12 columns · 32 margin · sidebar 240
One way back"← Issues" restores your filtered list. The focus bar, the duplicate crumb and the X are gone.
Readable stackApp frames open with code context. Library frames fold into one line. Raw is a toggle.
RailStatus, assignee, numbers, environment, release and fingerprint, always in that order.
Honest deltasAn increase in events or users on an issue is bad, so it's shown in red with an arrow.
27 · Page · Project overview

Project overview

One row of equal-height tiles, a severity chart with a prior-period ghost line, and "Needs attention" ranked by impact rather than raw count. Empty metrics say why they're empty. Switch the viewport to see it at 390, 820, 1280 and 1440.

Expanded · 12 columns · 32 margin · sidebar 240
Equal tilesTiles share a fixed height with label, value, delta and comparison. Long labels wrap instead of truncating ("+909% vs prior wi…").
PolarityErrors up is red, events up is neutral, users down is green.
N/A, not 0%Crash-free shows "—" and a link to enable sessions, instead of 0.0% in red.
Readable axesFive date ticks, no repeats, and severity colours from the shared ramp.
28 · Page · Settings

Settings: sections, save bar and danger zone

Settings pages use the narrow 720 column with a left sub-nav. Each section has a label column and a field column. Changes collect in a sticky save bar that appears only when the form is dirty. Destructive actions sit in a separate danger zone at the very bottom, each with its own confirmation dialog. Switch the viewport to see it at 390, 820, 1280 and 1440.

Expanded · 12 columns · 32 margin · sidebar 240
Save barSticky to the viewport bottom and visible only when dirty. Navigating away with unsaved changes shows a guard dialog.
Danger zoneLast on the page with a red border. Each action opens a dialog that states the consequence in numbers.
ValidationFields validate on blur, and errors include an icon and say how to fix the value.
Account pageCapability IDs become human labels. Org kill-switches move to a separate Organisation › Danger zone.
29 · Pages · phone and tablet

Pages on phone and tablet

The four page templates at 390 and 820, plus the phone-only flows: bottom sheets for pickers and filters, full-screen dialogs for forms, the More drawer and toasts that clear the bottom bar.

Issues list Cards, Filters (n) sheet, load more

Mobile · 390Issues list
Tablet · 820Issues list

Issue detail Back in the app bar, rail summary, scrolling tabs

Mobile · 390Issue detail
Tablet · 820Issue detail

Project overview 2-up tiles, swipeable chart

Mobile · 390Project overview
Tablet · 820Project overview

Settings Scrolling section tabs, single-column form, floating save bar

Mobile · 390Settings
Tablet · 820Settings

Phone flows sheets, full-screen dialogs, drawers and toasts

Events streamLive tail, load older
AI drawerFull screen below 720
More drawerEvery destination, grouped
Filters sheetReplaces the chip row
Environment sheetRadio list, remembers choice
Project switcherSearch + recent projects
Confirm sheetPrimary on top
Full-screen dialogForms with ≥ 3 fields
ToastAbove the bottom bar
30 · Accessibility & content

Accessibility and voice

The baseline is WCAG 2.2 AA. Flutter web semantics are on by default, not hidden behind "Tab → Enable accessibility".

Perceivable

  • All text tokens are ≥ 4.5:1 on canvas, surface, muted and raised.
  • Focus ring is ≥ 3:1 against adjacent colours (4.36 light, 7.02 dark).
  • Severity, status and env carry a glyph as well as colour.
  • Charts include an aria summary and a "View as table" option.

Operable

  • Hit targets are ≥ 44×44, including icon buttons and chips.
  • Tooltips show on hover and focus, and dismiss with Esc.
  • Every list has keyboard navigation. Every overlay traps and restores focus.
  • Reduced motion is respected throughout.

Voice (NZ English)

  • Plain, concrete and dry. Use "colour", "organisation", "analyse" and "behaviour".
  • Use Sentence case everywhere. Buttons are verbs ("Resolve", "Send fix").
  • Errors say what happened, then what to do. Don't blame the user or the time range when a filter is the cause.
  • Dates read "11 Oct 2026" and times read "14:02 NZDT".

Talaria Design System v2.1 · tokens.json is the source of truth · Made in Aotearoa New Zealand

Copied