Quiet by default.
Monochrome first. Ink does the work, and color only appears when it means something: pink for attention, data colors for charts and categories. Healthy states stay quiet; only exceptions get color.
inlay turns Relay's look into a shared system: its day and night themes, Inter with small mono labels, and a single pink accent for anything that needs attention. The structure follows Interfere's design system, so every color, size and timing is a named token. It is built for phones first, and Phosphor supplies every icon.
Monochrome first. Ink does the work, and color only appears when it means something: pink for attention, data colors for charts and categories. Healthy states stay quiet; only exceptions get color.
If you can't say what something does, remove it. Labels name things, numbers carry their units, and decoration is never the point.
Designed at 390 wide in one hand, then scaled up. 48px controls on touch, primary actions in the bottom third, bottom sheets instead of popovers, nothing that needs hover.
Every press shows a pressed state within 100 ms. Motion confirms what happened and never makes anyone wait. On phones, selection changes get a light haptic.
Numbered sections, small mono labels, units beside numbers. State is always a word plus a dot, never color alone.
Relay's two themes, made into tokens. Day is graphite on pale gray and white; night is charcoal and silver. Components only read semantic names, so every screen switches theme for free.
Open Color's grays, which Relay already uses, extended with three deep darks for night surfaces and terminals.
Focus rings, alerts and destructive actions, and nothing else. It is never decoration. Text on a pink fill is dark for contrast. When it shows up, something needs a person.
A fixed order for chart series and category tags. Data 1 is ink; 2 to 4 are blue, green and violet, tested for color-blind separation in both themes (closest neighbors ΔE ≥ 23). Never used for status.
bg → paper → raised gets lighter in day and in night, so layering reads without shadows. Dark (action cards, toasts) and terminal stay dark in both themes.
Relay's pairing. Inter for everything people read. IBM Plex Mono, small and uppercase, for labels, and in normal case for numbers with units and for code.
Titles, body, buttons, everything people read. The single-storey a and open digits come from the replay site.
Labels sit above or beside what they name and never wrap. Values with units, IDs, paths and code also use mono, in normal case.
Body is 15px. Inputs go to 16px on touch so iOS never zooms. Headlines track tighter as they grow.
Live numbers use the reading style with tabular figures, and the unit sits beside them in mono: 38 %, 2.4 GB.
9px labels are for metadata only. Anything someone needs to act on is 10px or larger.
Spacing, sizes and radii step in 4px. Layouts change at three window classes, and touch screens get bigger controls automatically.
pointer: coarse); the visible shape can stay smaller than the hit area.env(safe-area-inset-*). Nothing tappable under the home indicator.Hairline edges and tonal layers do most of the work. Shadows are only for things that float above the page.
Inner radius = outer radius − padding. A segmented control is 8 outside, 3 padding, 5 inside.
Status dots are 6px squares, as in Relay. Radios, switches and avatars are round.
Motion is short and purposeful. It confirms what happened and never holds anything up. Keyboard users and reduced-motion settings get none at all.
out = cubic-bezier(.23, 1, .32, 1); readings use in-out.
On phones, haptics mark the moments a person would want to feel: a selection changing, a long-press, a send. Never on ordinary taps.
| Token | When | Flutter | Web (Android) |
|---|---|---|---|
| selection | A segmented control, picker row or slider step changes | selectionClick() | vibrate(8) |
| press | A long-press opens a menu, or a drag picks something up | lightImpact() | vibrate(12) |
| confirm | A committed action: send, save, delete | mediumImpact() | vibrate(20) |
| warning | Something failed or needs attention | heavyImpact() ×2 | vibrate([30,60,30]) |
While content streams, haptics fire at most every 320 ms. They follow the system setting and always accompany a visible change.
Every icon is from Phosphor, the set Relay and replay already use. There are no custom illustrations: heroes and empty states put a Phosphor icon on a tile.
Icons take the text color: ink, or muted when inactive. Pink only inside a signal row or button, data colors only in a chart legend.
On phones, icons in navigation always have a word under them. Icon-only buttons need an aria-label (and a tooltip with a pointer).
Web: @phosphor-icons/web or @phosphor-icons/react. Flutter: phosphor_flutter, re-exported by the inlay package.
Every part is plain HTML plus a class, with variants as data attributes and state as ARIA. Each has a touch size and a pointer size, and says how it behaves on a phone.
Primary is ink, at most one per view. Secondary has a hairline. Ghost is for toolbars. Signal is only for destructive actions.
sm 32 · md 40 · lg 48 with a pointer; 40 · 48 · 56 on touch, from the same markup.
Moves down 1px while pressed. Full-width (data-block) primary buttons sit at the bottom of the screen.
2 to 4 options that switch a view in place. With 5 or more, use a sheet with a list.
Full width (data-block). Shortcut hints disappear on touch screens.
Applies immediately. Ink when on.
Part of a form that gets submitted. The tick is drawn in the on-ink color for each theme.
The row is the target: the whole label is tappable, and it is at least 44 tall.
Label above, field below, hint underneath. Units are a mono suffix inside the field; search gets a leading Phosphor icon.
Focus turns the edge to ink. Errors turn it pink and replace the hint with what went wrong and what to do.
Set inputmode and autocomplete. The keyboard must never cover the focused field or the primary action.
For a feel, not a number: volume, brightness, zoom. Show the value beside the label.
For exact values in small ranges: retries, copies, columns. Hold to repeat.
Relay's segmented meter, for levels: CPU, memory, disk, quota. Eases between readings over 500 ms. Turns pink past a threshold and says so in words.
A plain bar for tasks. Use a determinate bar whenever you know the size; the spinner is for short waits inside a control.
A 6px square. Gray is off, ink is on, pink needs attention, data colors only in legends.
Status. Always a word plus a dot. Healthy is ink, not green, because normal shouldn't stand out.
A filter you toggle. Selected chips go to paper with an ink edge.
Paper, hairline, radius 12, padding 20 (16 on small phones). Starts with a numbered section title.
A pale panel with a Phosphor icon on a 64 tile. One per screen, at the top.
The dark card leads somewhere. Title in Inter, subtitle in mono, an arrow at the end.
A 24 icon or a 40 tile, title, mono meta, then a trailing value, switch or chevron. Dividers inset to the text.
The whole row is the target. Swipe left reveals at most two actions; destructive is pink and last.
Bottom tab bar with 3 to 5 destinations: a 24 icon over a 10px label. The current tab uses the Fill icon, ink and bold, with a 2px bar. Hidden while the keyboard is up.
The 214px sidebar takes over, with numbered items.
Title or breadcrumbs, with up to two icon buttons. Primary actions do not go here on phones.
Menus, pickers and confirmations become bottom sheets: handle, title, close, then content, then the primary action at the bottom. Drag down or tap outside to dismiss.
The same actions as a menu, with shortcuts. Destructive items are last, in pink, below a separator.
Dark, floating above the tab bar, 4 s, one action at most. Swipe to dismiss. Never the only record of an error.
Stays until the problem is gone. Pink for trouble, neutral for information. Says what's happening and what happens next.
Three hairline gridlines, a 1.8px line, a faint area and a marker on the latest value. Readings ease; they never count up from zero on load.
One series is ink. Several take data colors 2, 3, 4 in order, with a legend of dots and words.
Alerts show up here when CPU, memory or disk cross a limit you set.
A Phosphor icon on a 64 tile, a title that says what's missing, one sentence on how it gets filled, and at most one action.
Plain and useful. No jokes and no illustrations.
Four screens built only from the parts above, at 360 wide: a Relay-style overview in both themes, a file browser, and settings with a sheet open.
tokens/inlay.tokens.json is the source of truth. node scripts/build.mjs writes the CSS, the Flutter constants and a flat JSON from it.
<!-- one file: fonts, tokens, base, components --> <link rel="stylesheet" href="inlay/dist/inlay.css"> <html data-theme="dark"> <!-- or leave it off to follow the system --> <!-- icons: Phosphor, regular weight (npm i @phosphor-icons/web) --> <button class="button" data-variant="primary"><i class="ph ph-plus"></i> Add server</button> <div class="meter" style="--value: 38"></div> <span class="pill" data-tone="signal">Disk 91%</span> .my-thing { background: var(--paper); color: var(--muted); border-radius: var(--radius-panel); }
# pubspec.yaml dependencies: inlay: { path: ../inlay/flutter } MaterialApp(theme: Inlay.theme(Brightness.light), darkTheme: Inlay.theme(Brightness.dark)); final p = Inlay.of(context); // p.paper, p.ink, p.signal, p.data2 … Text('CPU', style: Inlay.label(context)); InlayMeter(38); InlayDot(color: p.signal); InlaySectionTitle('01', 'CPU', trailing: 'Live / 2s'); InlayTile(PhosphorIconsRegular.hardDrives, large: true); // phosphor_flutter is re-exported InlayHaptics.selection(); // selection · press · confirm · warning
| Group | CSS | Flutter | Examples |
|---|---|---|---|
| Color | --paper | Inlay.of(c).paper | bg paper raised pale well dark terminal · ink muted faint · line line-strong · signal signal-ink signal-pale · data-1…4 |
| Type | .text-label, var(--text-body) | InlayType.label | display title-1…3 headline body-lg body body-sm caption reading label label-sm mono mono-sm code |
| Space | --space-4 | InlaySpace.s4 | 0 0_5 1 1_5 2 3 4 5 6 8 10 12 16 20 (× 4px) |
| Radius | --radius-panel | InlayRadius.panel | segment xs tag control panel overlay sheet full |
| Motion | --dur-quick, --ease-out | InlayMotion.quick | press 90 · quick 160 · surface 200 · page 250 · reading 500 |
| Icons | <i class="ph ph-house"> | PhosphorIconsRegular.house | Regular by default, Fill for the selected tab |
System follows your phone's setting. Terminals stay dark either way.