Skip to main content
Normal UI

Tokens

Edit tokens.json, then run pnpm tokens. In CSS use the custom property (var(--canvas)); in StyleX import the constant (color.canvas from src/tokens.stylex.ts), which compiles to the same var().

Colour

Each swatch is drawn inside a data-color-scheme box, so both color schemes show whatever the page is set to.

TokenLightDarkUsage
--canvas
color.canvas
oklch(100% 0 none)oklch(18.22% 0 none)Page background; the CSS system color Canvas. Every text token below is checked against it.
--canvas-text
color.canvasText
oklch(0% 0 none)oklch(100% 0 none)Body copy and headings on canvas. 21:1 light, 18.7:1 dark.
--surface-soft
color.surfaceSoft
oklch(97.91% 0 none)oklch(22.64% 0 none)The faintest tint, for light grouping (a soft card). Everything on canvas keeps its contrast here: gray-text 4.87:1 light, 7.59:1 dark; control-border 4.28:1, 6.06:1.
--surface-muted
color.surfaceMuted
oklch(96.12% 0 none)oklch(26.86% 0 none)A quieter panel for secondary content: muted and filled cards, recessed wells, separated bands. The darkest light tint that keeps gray-text at 4.5:1 (4.62:1; dark 6.74:1); control-border 4.06:1, 5.38:1.
--surface-raised
color.surfaceRaised
oklch(100% 0 none)oklch(30.92% 0 none)A panel set on a tinted surface (the body of a muted card): white in light, one step lighter in dark. gray-text 5.17:1 light, 5.88:1 dark; control-border 4.54:1, 4.69:1.
--link
color.link
oklch(42.9015% 0.2973 264.05)oklch(73.9% 0.139 282.5)Unvisited links on canvas, always underlined. 9.4:1 light, 7.8:1 dark. The browser's LinkText, unchanged.
--link-visited
color.linkVisited
oklch(37.84% 0.172 302.2)oklch(80.13% 0.1 308.1)Visited links on canvas. 11:1 light, 9.7:1 dark. The browser's VisitedText, unchanged.
--link-active
color.linkActive
oklch(59.6% 0.2446 29.2)oklch(79.78% 0.116 20)A link while it is being pressed, on canvas. 4.53:1 light (passes AA by a hair; never use it for resting text), 9.5:1 dark.
--button-face
color.buttonFace
oklch(95.21% 0 none)oklch(46.76% 0 none)Button fill. Dark is darkened from Chromium's #6b6b6b so white labels reach 6.9:1.
--button-text
color.buttonText
oklch(0% 0 none)oklch(100% 0 none)Button labels on button-face. 18.3:1 light, 6.9:1 dark.
--field
color.field
oklch(100% 0 none)oklch(35.23% 0 none)Fill of text inputs, textareas and selects.
--field-text
color.fieldText
oklch(0% 0 none)oklch(100% 0 none)Typed text on field. 21:1 light, 11.2:1 dark.
--control-border
color.controlBorder
oklch(56.58% 0 none)oklch(68.62% 0 none)1px border of buttons, fields, selects and fieldsets. At least 3:1 against canvas, field and button-face in both themes (light 4.5:1 on canvas, 3.95:1 on button-face; dark 6.7:1 on canvas, 4:1 on field).
--button-border
color.buttonBorder
{control-border}{control-border}Border of buttons. Same as control-border in Normal UI; a theme whose buttons are a solid fill may set it to button-face, as long as the face itself holds 3:1 against canvas.
--button-face-hover
color.buttonFaceHover
{button-face}{button-face}Button fill under the pointer. Same as button-face in Normal UI, where hover only darkens the border.
--button-border-hover
color.buttonBorderHover
{canvas-text}{canvas-text}Button border under the pointer: full-strength text colour.
--button-border-disabled
color.buttonBorderDisabled
{button-border}{button-border}Dashed border of a disabled button. Same as button-border; a theme whose buttons have no border gives it one here so the button still has an edge.
--button-edge
color.buttonEdge
transparenttransparentColour of the solid edge under a button (button-edge-width tall). None in Normal UI.
--button-pressed-face
color.buttonPressedFace
{canvas-text}{canvas-text}Fill of a toggle button that is on (aria-pressed): inverted.
--button-pressed-text
color.buttonPressedText
{canvas}{canvas}Label of a toggle button that is on, on button-pressed-face.
--button-primary-face
color.buttonPrimaryFace
{button-face}{button-face}Fill of the one button a form leads with (variant="primary"). Normal UI draws every variant the same: order and wording carry the emphasis. A theme may set it apart.
--button-primary-text
color.buttonPrimaryText
{button-text}{button-text}Label on button-primary-face.
--button-primary-border
color.buttonPrimaryBorder
{button-border}{button-border}Border of a primary button.
--button-primary-face-hover
color.buttonPrimaryFaceHover
{button-face-hover}{button-face-hover}Primary button fill under the pointer.
--button-primary-edge
color.buttonPrimaryEdge
{button-edge}{button-edge}Edge under a primary button.
--button-warning-face
color.buttonWarningFace
{button-face}{button-face}Fill of a button that destroys or cannot be undone (variant="warning"). Identical to a plain button in Normal UI; the label says what it does.
--button-warning-text
color.buttonWarningText
{button-text}{button-text}Label on button-warning-face.
--button-warning-border
color.buttonWarningBorder
{button-border}{button-border}Border of a warning button.
--button-warning-face-hover
color.buttonWarningFaceHover
{button-face-hover}{button-face-hover}Warning button fill under the pointer.
--button-warning-edge
color.buttonWarningEdge
{button-edge}{button-edge}Edge under a warning button.
--placeholder
color.placeholder
oklch(53.48% 0 none)oklch(74.76% 0 none)Placeholder text on field. 5.2:1 light, 5:1 dark. Replaces opacity-faded placeholders that drop under 4.5:1.
--gray-text
color.grayText
oklch(53.48% 0 none)oklch(74.76% 0 none)Disabled labels and secondary notes on canvas. 5.2:1 light, 8.4:1 dark: disabled text stays readable; the dashed border says it is disabled.
--accent
color.accent
oklch(53.51% 0.199 258.1)oklch(81.97% 0.093 252.8)accent-color for checkboxes, radios, range, progress and the switch track. A non-text mark, but kept at text strength: 5.3:1 on white (light), 10.8:1 on dark canvas. Deepened from Chromium's #0075ff (4.2:1).
--focus-ring
color.focusRing
oklch(50.69% 0.185 257.7)oklch(81.97% 0.093 252.8)The 2px solid :focus-visible outline, offset 2px onto canvas. 6:1 light, 10.8:1 dark; also 5.2:1 on light button-face.
--focus-inset
color.focusInset
transparenttransparentA second ring drawn just inside a focused field or list item, for themes whose focus-ring is light (a yellow ring needs a dark inset to hold 3:1 on white). None in Normal UI.
--mark
color.mark
oklch(96.798% 0.211 109.8)oklch(96.798% 0.211 109.8)<mark> highlight fill, the same yellow in both themes.
--mark-text
color.markText
oklch(0% 0 none)oklch(0% 0 none)Text on mark, black in both themes (19.6:1). Never let <mark> inherit white dark-theme text.
--highlight
color.highlight
oklch(86.28% 0.067 254)oklch(41.84% 0.083 250.4)::selection background.
--highlight-text
color.highlightText
oklch(0% 0 none)oklch(100% 0 none)Selected text on highlight, links included. 13.9:1 light, 8.5:1 dark.
--rule
color.rule
{control-border}{control-border}<hr> and table cell borders. Same as control-border, so dividers stay visible at 3:1+.
--status-error
color.statusError
{canvas-text}{canvas-text}Border and lead word of error messages and invalid fields. Text colour in Normal UI: the words say it is an error, never colour alone. A theme may give it a hue at 4.5:1.
--status-warning
color.statusWarning
{canvas-text}{canvas-text}Border and lead word of warnings.
--status-success
color.statusSuccess
{canvas-text}{canvas-text}Border and lead word of success messages.
--status-info
color.statusInfo
{canvas-text}{canvas-text}Border and lead word of notes.
--page
color.page
{canvas}{canvas}Behind a .pd-sheet page. Same as canvas in Normal UI, so a sheet is just the page.
--masthead
color.masthead
{canvas}{canvas}Fill of a .pd-masthead band. Same as canvas in Normal UI; a theme may make it a brand colour.
--masthead-text
color.mastheadText
{canvas-text}{canvas-text}Text and links on masthead.

Type

--font-serif / font.serif: "Times New Roman", Times, serif

--font-sans / font.sans: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"

--font-mono / font.mono: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, "Courier New", monospace

Headings (content)

StyleSampleUsage
h1
32px / 42px · 700
The quick brown foxPage title in long-form content. 2rem on a 42px line (snapped to quarter lines). No space above; half a line below.
h2
24px / 30px · 700
Section headingSection heading. 1.5rem; 1.5 lines above, half a line below.
h3
18.72px / 24px · 700
Subsection headingSubsection. 1.17rem; one line above, half a line below.
h4
16px / 24px · 700
Minor headingMinor heading, same size as body. 1em.
h5
14px / 18px · 700
Small headingRaised from the default 13.28px (0.83em) to 14px.
h6
14px / 18px · 700
SMALLEST HEADINGRaised from the default 10.72px (0.67em) to 14px; set apart from h5 by uppercase and letter-spacing.

Headings (app)

StyleSampleUsage
app-h1
24px / 32px · 700
Freight performancePage title inside .pd-app. No margins; the layout gap spaces it.
app-h2
18px / 24px · 700
ExceptionsPanel and section titles inside .pd-app.
app-h3
16px / 20px · 700
Customs holdGroup titles inside a panel.
app-h4
15px / 20px · 700
SeaMinor labels that are still headings.
app-h5
14px / 20px · 700
NotesRare; prefer fewer levels in UI.

Text

StyleSampleUsage
body
16px / 1.5 · 400
Plain text on a plain page, readable by everyone.All running text, in the system UI font. 16px on a 24px line: the rhythm unit. Paragraphs, lists and tables leave one line below them in content, none in app UI.
small
13.28px / 1.5 · 400
Fine print and captions<small>, captions and notes. 0.83em. Never smaller.

Controls

StyleSampleUsage
control
16px / 1.25 · 400
Submit orderButtons, inputs, selects, textareas. 16px, same family as the page.

Code

StyleSampleUsage
code
16px / 1.5 · 400
font: inherit;<code>, <kbd>, <samp>, <pre>. 1em of the surrounding text, in the platform monospace.

Spacing

TokenValueUsage
--space-2xs
space.xxs
2pxFocus-ring offset; legend side padding.
--space-xs
space.xs
4pxVertical padding in buttons, fields and table cells.
--space-sm
space.sm
8pxBody margin (the browser's own 8px); horizontal padding in buttons and table cells.
--space-md
space.md
16pxDefault gap in .pd-stack and between panels; panel padding.
--space-lg
space.lg
24pxMinimum target size for any control (WCAG 2.5.8).
--space-indent
space.indent
40pxList indent and blockquote side margin, as the browser sets them.

Radius

TokenValueUsage
--radius-none
radius.none
0Everything that is not a control: tables, fieldsets, images, mark.
--radius-control
radius.control
2pxButtons, text fields, selects: the slight rounding browsers draw.
--radius-round
radius.round
50%Radio buttons only.

Shape

Borders, target size, button edges and the focus ring. Normal UI's values reproduce the browser's look; themes change them.

TokenValueUsage
--border-width
shape.borderWidth
1pxStructural borders: cards, panels, popups, menus, tabs, separators.
--control-border-width
shape.controlBorderWidth
1pxBorder width of buttons, text fields, selects and textareas. A theme may thicken it; it never goes below 1px.
--target-min
shape.targetMin
24pxMinimum height and width of any control (WCAG 2.5.8). Rows in menus and lists add to it.
--button-padding-block
shape.buttonPaddingBlock
4pxSpace above and below a button label.
--button-padding-inline
shape.buttonPaddingInline
8pxSpace either side of a button label.
--button-edge-width
shape.buttonEdgeWidth
0pxHeight of the solid edge under a button (button-edge). 0 in Normal UI.
--button-shadow
shape.buttonShadow
0 0 0 transparentAny further shadow on a button at rest, e.g. a bevel. Nothing in Normal UI. Must be a shadow list, not none.
--button-shadow-active
shape.buttonShadowActive
0 0 0 transparentThe whole button shadow while pressed (:active); replaces the edge and button-shadow.
--button-press-offset
shape.buttonPressOffset
0pxHow far a button moves down (and right, for a bevel) while pressed.
--focus-width
shape.focusWidth
2pxWidth of the :focus-visible outline. Never below 2px (WCAG 2.4.11 / 2.4.13).
--focus-offset
shape.focusOffset
2pxGap between an element and its focus outline.
--choice-size
shape.choiceSize
18pxCheckbox and radio size: up from the browser's 13px.
--sheet-shadow
shape.sheetShadow
noneShadow under a .pd-sheet. None in Normal UI.

Text treatments

Heading sizes, control text, eyebrows and the link treatment in navigation, as values a theme can change.

TokenValueUsage
--control-font-size
text.controlFontSize
1remText in buttons, inputs, selects, textareas: grows with the reader's text size.
--control-line-height
text.controlLineHeight
1.25Line height inside controls.
--line-height-body
text.lineHeightBody
1.5Running text: the 24px rhythm line at 16px.
--heading-weight
text.headingWeight
700Every heading.
--heading-1
text.heading1
2remh1 in content.
--heading-2
text.heading2
1.5remh2 in content.
--heading-3
text.heading3
1.17remh3 in content.
--heading-4
text.heading4
1remh4 in content.
--heading-5
text.heading5
0.875remh5 in content.
--heading-6
text.heading6
0.875remh6 in content, set apart by uppercase.
--app-heading-1
text.appHeading1
1.5remh1 inside .pd-app.
--app-heading-2
text.appHeading2
1.125remh2 inside .pd-app.
--app-heading-3
text.appHeading3
1remh3 inside .pd-app.
--app-heading-4
text.appHeading4
0.9375remh4 inside .pd-app.
--app-heading-5
text.appHeading5
0.875remh5 and h6 inside .pd-app.
--small-min
text.smallMin
13pxFloor for <small> and other fine print: 0.83em, never under this, whatever the root size.
--error-weight
text.errorWeight
400Weight of a field's error message. Normal in Normal UI; the words and the doubled field border carry it.
--eyebrow-size
text.eyebrowSize
0.8125remSmall label above a group (nav sections, card kickers).
--eyebrow-weight
text.eyebrowWeight
700Eyebrow weight.
--eyebrow-transform
text.eyebrowTransform
uppercaseEyebrow case.
--eyebrow-tracking
text.eyebrowTracking
0.04emEyebrow letter-spacing.
--nav-link-decoration
text.navLinkDecoration
underlineLinks inside <nav>. Underlined in Normal UI. A theme may set none: a link in a navigation list is identified by where it sits (1.4.1); links in running text always stay underlined.