Ottawa Senators news, video and talk YouTube

Coming In Hot

Design system

Every token, type size, motif, component and screen the site is built from, in one place. Values on this page are read live from the running site, so they are always the real ones.

The brand, in plain English
Tokens
397
Contrast pairs at AA
57/57
Motifs
5
Icons
56

Values shown from lib/tokens.mjs. They are re-read from the page once it loads.

Principles

Five rules that settle most design questions before they become meetings.

  1. Broadcast, not template

    Pages should feel like a sports network on game night: condensed type, hard edges, red, black and white. If it could be any blog, it is not done.

  2. Red earns attention

    Red marks the one thing to tap or notice. Most of every page is white, paper and black, so the red still means something.

  3. Content first, chrome second

    Articles, video and merch are the reason people come. Motifs frame the content; they never sit on top of it or slow it down.

  4. Honest by default

    Sample content says Sample. Ads say Ad. Nothing claims to be official, and nothing invents a quote, a score or a fact.

  5. Built to be added to

    New shows, writers and sections reuse these tokens and parts, so they arrive on brand without a redesign or a new colour.

Colour

Three layers. Primitives are the raw ramps. Semantic tokens name a job (text, surface, focus) and point at a primitive. Components and component tokens use semantic tokens, and the token guard warns on any alias or primitive reference in app/ or components/, so changing the red is one line in lib/tokens.mjs.

Primitive ramps

Each chip shows its contrast as text on white and on broadcast black. AA needs 4.5:1 for body text and 3:1 for large text and UI parts.

Red

The brand red and its steps. 600 is the brand red; 400 is the red that reads on black.

  • --red-50#FDECEESoft red wash behind warnings and highlightsOn white 1.1:1FailOn black 17.2:1AAA
  • --red-100#FBD5D9On white 1.3:1FailOn black 14.6:1AAA
  • --red-200#F6AAB2On white 1.9:1FailOn black 10.6:1AAA
  • --red-300#FF7A84On white 2.5:1FailOn black 7.8:1AAA
  • --red-400#FF4D5AHot red: red text and accents on blackOn white 3.3:1AA largeOn black 6.1:1AA
  • --red-500#E8192FOn white 4.6:1AAOn black 4.3:1AA large
  • --red-600#D50F25Coming In Hot red, the brand colourOn white 5.4:1AAOn black 3.7:1AA large
  • --red-700#A30D1CDeep red: hoverOn white 8.0:1AAAOn black 2.5:1Fail
  • --red-800#760915PressedOn white 11.5:1AAAOn black 1.7:1Fail
  • --red-900#4A050DOn white 15.9:1AAAOn black 1.2:1Fail

Ink (blacks and cool greys)

Near-blacks for the masthead, footer and video bands, stepping up to the greys used for secondary text and control borders.

  • --ink-1000#000000Pure black: the masthead top stripOn white 21.0:1AAAOn black 1.1:1Fail
  • --ink-950#0B0B0CBroadcast black: masthead, footer, video bandsOn white 19.7:1AAAOn black 1.0:1Fail
  • --ink-900#121315Body text on lightOn white 18.6:1AAAOn black 1.1:1Fail
  • --ink-850#1B1C1FRaised panels on blackOn white 17.0:1AAAOn black 1.2:1Fail
  • --ink-800#2A2B30Lines on blackOn white 14.1:1AAAOn black 1.4:1Fail
  • --ink-700#3D3F45On white 10.5:1AAAOn black 1.9:1Fail
  • --ink-600#5C5F66Secondary text on lightOn white 6.4:1AAOn black 3.1:1AA large
  • --ink-550#73767DPlaceholder text (4.5:1 on white)On white 4.5:1AAOn black 4.3:1AA large
  • --ink-500#8A8D94Form control borders (3:1 on white)On white 3.3:1AA largeOn black 5.9:1AA
  • --ink-400#A3A6ADSecondary text on blackOn white 2.4:1FailOn black 8.1:1AAA
  • --ink-300#B9BBC0On white 1.9:1FailOn black 10.2:1AAA
  • --ink-200#D4D6DAOn white 1.5:1FailOn black 13.5:1AAA
  • --ink-100#EBECEEOn white 1.2:1FailOn black 16.6:1AAA

Paper (whites and warm greys)

Warm off-whites for the page, so white cards lift off it, and the warm greys used for lines and image wells.

  • --paper-0#FFFFFFWhiteOn white 1.0:1FailOn black 19.7:1AAA
  • --paper-50#F7F6F3Lightest washOn white 1.1:1FailOn black 18.2:1AAA
  • --paper-100#F5F4F1The page backgroundOn white 1.1:1FailOn black 17.9:1AAA
  • --paper-150#F0EEEAImage wells behind product photosOn white 1.2:1FailOn black 17.0:1AAA
  • --paper-200#EBE9E4Loading skeletonsOn white 1.2:1FailOn black 16.2:1AAA
  • --paper-300#E3E1DCHairlines and card bordersOn white 1.3:1FailOn black 15.1:1AAA
  • --paper-400#CFCCC4On white 1.6:1FailOn black 12.3:1AAA
  • --paper-500#B9B6AEDashed placeholder bordersOn white 2.0:1FailOn black 9.7:1AAA
  • --paper-700#6B675FOn white 5.6:1AAOn black 3.5:1AA large

Gold (restrained accent)

Used sparingly. 400 is the bright gold of the Sample label, demo notes and the focus ring on black; 500 is the muted broadcast gold for thin rules, badge rings and small labels. Never large fills, never a brand colour on its own.

  • --gold-50#FFF8DBDemo note backgroundOn white 1.1:1FailOn black 18.5:1AAA
  • --gold-100#FFF0B8On white 1.1:1FailOn black 17.2:1AAA
  • --gold-200#E8D48ADemo note borderOn white 1.5:1FailOn black 13.3:1AAA
  • --gold-400#FFD54ASample label, focus ring on blackOn white 1.4:1FailOn black 13.9:1AAA
  • --gold-500#C9A24ABroadcast gold: thin rules, badge rings, small labels on blackOn white 2.4:1FailOn black 8.2:1AAA
  • --gold-600#C99700Gold text on blackOn white 2.7:1FailOn black 7.4:1AAA
  • --gold-700#8A6A00Gold text on whiteOn white 5.1:1AAOn black 3.9:1AA large

Status

Green for success, blue for keyboard focus on light backgrounds. Red doubles as the danger colour.

  • --green-50#E8F4ECSuccess backgroundOn white 1.1:1FailOn black 17.4:1AAA
  • --green-300#4FBF75Success on blackOn white 2.3:1FailOn black 8.5:1AAA
  • --green-500#2D8A4EOn white 4.3:1AA largeOn black 4.6:1AA
  • --green-600#1C7C3ASuccess text and iconsOn white 5.3:1AAOn black 3.7:1AA large
  • --blue-500#1F6FEBFocus ring on lightOn white 4.6:1AAOn black 4.2:1AA large
  • --blue-700#1D4AA8Rink blue linesOn white 8.1:1AAAOn black 2.4:1Fail
  • --blue-200#B9D3F0Rink creaseOn white 1.5:1FailOn black 12.8:1AAA
  • --ice-50#F3F7FARink iceOn white 1.1:1FailOn black 18.3:1AAA
  • --ice-150#DDE7EFRink ice, worn and shadedOn white 1.3:1FailOn black 15.7:1AAA

Alpha (see-through steps)

Translucent black, white and red for shadows, scrims and the speed-slash textures. Each chip shows the step over paper and over black.

  • --ink-a05#0b0b0c0d
  • --ink-a08#0b0b0c14
  • --ink-a10#0b0b0c1a
  • --ink-a12#0b0b0c1f
  • --ink-a16#0b0b0c29
  • --ink-a18#0b0b0c2e
  • --ink-a25#0b0b0c40
  • --ink-a45#0b0b0c73
  • --ink-a50#0b0b0c80
  • --ink-a72#0b0b0cb8
  • --gold-a35#c9a24a59
  • --white-a05#ffffff0d
  • --white-a10#ffffff1a
  • --white-a75#ffffffbf
  • --white-a90#ffffffe6
  • --red-a35#d50f2559
  • --red-a50#d50f2580
  • --blue-a30#1f6feb4d

Semantic tokens

What components actually use. The Points to column is the primitive each one lands on.

Surfaces

Surfaces tokens
SwatchTokenPoints toValueUse
--bg--paper-100#f5f4f1Page background
--surface--paper-0#ffffffCards, panels, forms
--surface-subtle--paper-50#f7f6f3Quiet wash inside a card
--surface-sunken--paper-150#f0eeeaImage wells behind photos
--surface-muted--paper-300#e3e1dcMuted chips and disabled fields
--surface-inverse--ink-950#0b0b0cBlack bands: masthead, footer, video
--surface-inverse-raised--ink-850#1b1c1fPanels sitting on black
--surface-inverse-pressed--ink-800#2a2b30Black controls while pressed
--surface-inverse-deep--ink-1000#000000The deepest strip at the very top
--surface-accent--red-600#d50f25Red bands: the charity push, primary buttons
--surface-accent-subtle--red-50#fdeceeSoft red behind errors and highlights
--surface-note--gold-50#fff8dbDemo notes
--surface-success--green-50#e8f4ecSuccess messages
--surface-dark--ink-950#0b0b0cAlias of surface-inverse

Text

Text tokens
SwatchTokenPoints toValueUse
--text--ink-900#121315Body text
--text-strong--ink-950#0b0b0cHeadlines and outlined controls
--text-muted--ink-600#5c5f66Dates, captions, secondary text
--text-placeholder--ink-550#73767dPlaceholder text in fields
--text-accent--red-600#d50f25Red text on light: eyebrows, links
--text-accent-strong--red-700#a30d1cRed text hover, red text on red tint
--text-inverse--paper-0#ffffffText on black
--text-inverse-muted--ink-400#a3a6adSecondary text on black
--text-accent-on-dark--red-400#ff4d5aRed text on black
--text-on-accent--paper-0#ffffffText on red
--text-on-color-soft--white-a90#ffffffe6Softer text on red or black photos
--text-gold-on-dark--gold-600#c99700Gold text on black (rare)
--text-success--green-600#1c7c3aSuccess text
--text-success-on-dark--green-300#4fbf75Success text on black
--text-danger--red-600#d50f25Error text

Borders

Borders tokens
SwatchTokenPoints toValueUse
--border--paper-300#e3e1dcHairlines and card borders
--border-strong--ink-950#0b0b0cBlack rules under section headers
--border-input--ink-500#8a8d94Form control borders (3:1 on white)
--border-input-hover--ink-600#5c5f66Form control borders on hover
--border-inverse--ink-800#2a2b30Lines on black
--border-accent--red-600#d50f25Red rules
--border-dashed--paper-500#b9b6aeDashed placeholder outlines (ads, sponsors)
--border-note--gold-200#e8d48aDemo note border

Accent and status

Accent and status tokens
SwatchTokenPoints toValueUse
--accent--red-600#d50f25The brand accent
--accent-hover--red-700#a30d1cAccent on hover
--accent-active--red-800#760915Accent while pressed
--accent-on-dark--red-400#ff4d5aAccent on black
--highlight--gold-400#ffd54aRestrained gold highlight
--accent-gold--gold-500#c9a24aMuted broadcast gold: thin rules, badge rings, small labels
--vendor-accent--red-600#d50f25The asterisk in Zavi Digital's wordmark on the invoice, in the client's red
--danger--red-600#d50f25Errors
--danger-surface--red-50#fdeceeError background
--success--green-600#1c7c3aSuccess
--success-surface--green-50#e8f4ecSuccess background

Focus

Focus tokens
SwatchTokenPoints toValueUse
--focus--blue-500#1f6febKeyboard focus ring on light
--focus-on-dark--gold-400#ffd54aKeyboard focus ring on black or red
--focus-ring-color--blue-500#1f6febThe ring in the current context (switches inside .on-dark)
--focus-halo--blue-a30#1f6feb4dSoft halo around a focused field
--focus-ring-widthBase value3px
--focus-ring-offsetBase value2px

Labels, notes and overlays

Labels, notes and overlays tokens
SwatchTokenPoints toValueUse
--sample-bg--gold-400#ffd54aSample label fill
--sample-fg--ink-950#0b0b0cSample label text
--sample-border--ink-950#0b0b0cSample label outline
--note-bg--gold-50#fff8dbDemo note fill
--note-border--gold-200#e8d48aDemo note border
--note-accent--gold-400#ffd54aDemo note left rule
--overlay-scrim--ink-a72#0b0b0cb8Behind dialogs and photo captions
--on-red--paper-0#ffffffAnything on red
--on-ink--paper-0#ffffffAnything on black
--on-ink-muted--ink-400#a3a6adQuieter things on black

Tints (see-through overlays)

Tints (see-through overlays) tokens
SwatchTokenPoints toValueUse
--tint-ink-faint--ink-a05#0b0b0c0dFaint dark lines on light: pinstripes, skate marks
--tint-ink--ink-a16#0b0b0c29Dark texture over red
--tint-ink-shade--ink-a25#0b0b0c40Shade at the foot of a photo panel
--tint-ink-half--ink-a50#0b0b0c80Half-strength black
--tint-white-faint--white-a05#ffffff0dFaint light texture over black
--tint-white--white-a10#ffffff1aLight wash and hairlines on black
--tint-white-strong--white-a75#ffffffbfLight catching a surface
--tint-accent-glow--red-a35#d50f2559Soft red glow
--tint-accent-half--red-a50#d50f2580Half-strength red

Ads, skeletons and media

Ads, skeletons and media tokens
SwatchTokenPoints toValueUse
--ad-stripe-a--paper-150#f0eeeaAd slot hatching, dark stripe
--ad-stripe-b--paper-50#f7f6f3Ad slot hatching, light stripe
--ad-border--paper-500#b9b6aeAd slot outline
--skeleton-base--paper-200#ebe9e4Loading skeleton
--skeleton-shine--paper-50#f7f6f3Loading skeleton shimmer
--media-well--paper-150#f0eeeaBehind product photos and thumbnails
--mask-opaque--ink-1000#000000Solid stop in CSS masks (only the alpha matters)

Brand aliases

The short names the site was first built with. They still resolve, but they point straight at the ramps, so no component uses them: the token guard warns if one appears in app/ or components/.

Brand alias tokens
SwatchTokenPoints toValueUse
--red--red-600#d50f25Coming In Hot red
--red-deep--red-700#a30d1cHover and pressed red
--red-hot--red-400#ff4d5aRed text and accents on black
--red-tint--red-50#fdeceeSoft red wash
--black--ink-950#0b0b0cBroadcast black
--ink--ink-900#121315Body text
--charcoal--ink-850#1b1c1fRaised panels on black
--graphite--ink-800#2a2b30Lines on black
--white--paper-0#ffffffWhite
--paper--paper-100#f5f4f1Page background
--line--paper-300#e3e1dcHairlines on light
--gold--gold-500#c9a24aRestrained broadcast gold: thin rules, badge rings, small labels

Component tokens

Per-component knobs built from semantic tokens. Change a button colour here, not in Button.module.css.

States (shared) 3
States (shared) tokens
TokenPoints toValueUse
--state-disabled-opacityBase value0.45Disabled controls fade to this
--state-pressed-shiftBase value1pxButtons nudge down this far when pressed
--state-hover-liftBase value-2pxInteractive cards lift this far on hover
Button 26
Button tokens
SwatchTokenPoints toValueUse
--btn-height-smBase value36px
--btn-heightBase value44pxMinimum tap target
--btn-height-lgBase value52px
--btn-primary-bg--red-600#d50f25
--btn-primary-bg-hover--red-700#a30d1c
--btn-primary-bg-active--red-800#760915
--btn-primary-fg--paper-0#ffffff
--btn-secondary-bg--ink-950#0b0b0c
--btn-secondary-bg-hover--ink-850#1b1c1f
--btn-secondary-bg-active--ink-800#2a2b30
--btn-secondary-fg--paper-0#ffffff
--btn-outline-fg--ink-950#0b0b0c
--btn-outline-bg-hover--ink-950#0b0b0c
--btn-outline-bg-active--ink-850#1b1c1f
--btn-outline-fg-hover--paper-0#ffffff
--btn-ghost-fg--red-600#d50f25
--btn-ghost-fg-hover--red-700#a30d1c
--btn-ghost-fg-active--red-800#760915
--btn-inverse-bg--paper-0#ffffff
--btn-inverse-bg-hover--paper-100#f5f4f1
--btn-inverse-bg-active--paper-300#e3e1dc
--btn-inverse-fg--ink-950#0b0b0c
--btn-outline-inverse-fg--paper-0#ffffff
--btn-outline-inverse-bg-hover--paper-0#ffffff
--btn-outline-inverse-bg-active--paper-300#e3e1dc
--btn-outline-inverse-fg-hover--ink-950#0b0b0c
Form fields 12
Form fields tokens
SwatchTokenPoints toValueUse
--field-heightBase value48px
--chevron-insetBase value1remGap from a dropdown's right edge to its chevron (every select and menu trigger), about the text's left padding
--field-bg--paper-0#ffffff
--field-fg--ink-900#121315
--field-border--ink-500#8a8d94
--field-border-hover--ink-600#5c5f66
--field-border-focus--blue-500#1f6feb
--field-halo-focus--blue-a30#1f6feb4d
--field-border-invalid--red-600#d50f25
--field-halo-invalid--red-50#fdecee
--field-placeholder--ink-550#73767d
--field-bg-disabled--paper-50#f7f6f3
Navigation and tabs 10
Navigation and tabs tokens
SwatchTokenPoints toValueUse
--nav-fg--paper-0#ffffffNav links on the black masthead
--nav-fg-muted--ink-400#a3a6adQuieter nav links (admin bar)
--nav-indicator--paper-0#ffffffUnderline under the current page
--nav-indicator-accent--red-400#ff4d5aRed underline (admin bar, drawer)
--tab-bg--paper-0#ffffff
--tab-fg--ink-600#5c5f66
--tab-fg-hover--ink-900#121315
--tab-bg-hover--paper-50#f7f6f3
--tab-bg-selected--ink-950#0b0b0c
--tab-fg-selected--paper-0#ffffff
Motifs 18
Motifs tokens
SwatchTokenPoints toValueUse
--slash-angleBase value-18degThe lean of every speed slash
--slash-shade--ink-a16#0b0b0c29Dark speed slashes over red
--slash-light--white-a05#ffffff0dFaint speed slashes over black
--slash-ghost-red--red-a50#d50f2580The trailing half-strength red slash
--slash-ghost-ink--ink-a50#0b0b0c80The trailing half-strength black slash
--texture-pinstripe--ink-a05#0b0b0c0dFine diagonal pinstripe on light cards
--ice-base--paper-50#f7f6f3Ice surface
--ice-scratch--ink-a05#0b0b0c0dSkate marks on the ice
--ice-sheen--white-a75#ffffffbfLight catching the ice
--boards-line--red-600#d50f25The red line
--boards-kick--ink-950#0b0b0cThe black kick plate under it
--scorebug-bg--ink-950#0b0b0c
--scorebug-fg--paper-0#ffffff
--scorebug-label-bg--red-600#d50f25
--scorebug-muted--ink-400#a3a6ad
--numeral-stroke--ink-950#0b0b0c
--numeral-stroke-on-dark--paper-0#ffffff
--numeral-shadow--red-600#d50f25
Locker Room (team page) 51
Locker Room (team page) tokens
SwatchTokenPoints toValueUse
--band-red--red-400#ff4d5aFrame for a player 1st to 10th in the NHL in his headline stat
--band-red-glow--red-a50#d50f2580Glow around a top 10 card and its platform ring
--band-gold--gold-500#c9a24aHairline frame for 11th to 50th in the NHL
--band-gold-text--gold-400#ffd54aRank chip text on a gold card
--band-gold-glow--gold-a35#c9a24a59Glow around a gold card and its platform ring
--band-silver--ink-200#d4d6daHairline frame for 51st to 250th in the NHL
--band-silver-glow--white-a10#ffffff1aThe light around a silver card
--band-graphite--ink-500#8a8d94Frame for every other card
--band-graphite-glow--white-a05#ffffff0dThe faint light around a graphite card
--band-red-1--red-400#ff4d5aRed frame gradient: the lit edge
--band-red-2--red-700#a30d1cRed frame gradient: the deep middle
--band-red-3--gold-400#ffd54aRed frame gradient: the gold catch light
--band-gold-1--gold-200#e8d48aGold frame gradient: the lit edge
--band-gold-2--gold-500#c9a24aGold frame gradient: the deep middle
--band-gold-3--gold-100#fff0b8Gold frame gradient: the catch light
--band-silver-1--ink-100#ebeceeSilver frame gradient: the lit edge
--band-silver-2--ink-500#8a8d94Silver frame gradient: the deep middle
--band-silver-3--ink-200#d4d6daSilver frame gradient: the catch light
--band-graphite-1--ink-600#5c5f66Graphite frame gradient: the lit edge
--band-graphite-2--ink-800#2a2b30Graphite frame gradient: the deep middle
--band-graphite-3--ink-400#a3a6adGraphite frame gradient: the catch light
--sweater-top--ink-700#3d3f45The sweater body, at the shoulders
--sweater-mid--ink-850#1b1c1fThe sweater body, at the chest
--sweater-bottom--ink-950#0b0b0cThe sweater body, at the hem
--segment-off--ink-800#2a2b30An unlit segment in a meter bar
--art-shadow--ink-a72#0b0b0cb8The drop shadow under the sweater art and its number
--rink-ice--ice-50#f3f7faThe ice of the rink and the shot maps
--rink-ice-shade--ice-150#dde7efWorn ice toward the boards
--rink-sheen--white-a90#ffffffe6The light on fresh ice
--rink-red--red-600#d50f25Centre red line, goal lines, faceoff circles
--rink-blue--blue-700#1d4aa8Blue lines and the centre circle
--rink-crease--blue-200#b9d3f0Goal creases
--rink-boards--ink-850#1b1c1fThe boards around the ice
--rink-kick--gold-400#ffd54aThe kick plate along the bottom of the boards
--rink-panel--paper-300#e3e1dcBlank board panels, never a logo
--rink-them--ink-600#5c5f66Opponent events on the rink
--rink-us--red-600#d50f25Senators events on the rink
--rink-ink--ink-900#121315Numbers and labels on the ice
--rink-pulse--red-a50#d50f2580The pulse around a goal
--medal-gold--gold-500#c9a24aAn earned honour medal
--medal-gold-hi--gold-400#ffd54aThe lit edge of an earned medal
--medal-ink--ink-950#0b0b0cThe glyph on an earned medal
--medal-locked--ink-700#3d3f45The outline of a medal not earned yet
--medal-locked-text--ink-400#a3a6adThe glyph and lock on a medal not earned yet
--platform-floor--white-a10#ffffff1aThe lit floor under the sweater art
--platform-beam--white-a05#ffffff0dThe light falling on the sweater art
--card-face--ink-850#1b1c1fPlayer card face
--card-face-deep--ink-1000#000000The darkest part of a card: the art well, the sweater
--card-line--ink-800#2a2b30Hairlines on a card
--holo-a--white-a05#ffffff0dHolo shimmer, soft band
--holo-b--white-a10#ffffff1aHolo shimmer, bright band

Contrast pairs

Every text and UI pairing the site uses, checked against WCAG 2.2. The build fails if any pair drops below AA. 57 of 57 pass.

Contrast ratios of text and background token pairs
SampleForeground on backgroundRatioLevelWhere
Aa--texton--bg16.90:1AAAText, 4.5:1Body text on the page
Aa--texton--surface18.59:1AAAText, 4.5:1Body text on cards
Aa--text-mutedon--bg5.81:1AAText, 4.5:1Dates and captions on the page
Aa--text-mutedon--surface6.40:1AAText, 4.5:1Dates and captions on cards
Aa--text-mutedon--surface-muted4.89:1AAText, 4.5:1Muted chip text
Aa--text-placeholderon--field-bg4.55:1AAText, 4.5:1Placeholder text in fields
Aa--text-accenton--surface5.35:1AAText, 4.5:1Red links and eyebrows on cards
Aa--text-accenton--bg4.87:1AAText, 4.5:1Red links and eyebrows on the page
Aa--text-accent-strongon--surface-accent-subtle7.00:1AAText, 4.5:1Red text on the red tint
Aa--text-dangeron--surface5.35:1AAText, 4.5:1Field errors
Aa--text-on-accenton--surface-accent5.35:1AAText, 4.5:1White on red: primary buttons, charity band
Aa--text-on-accenton--accent-hover7.98:1AAAText, 4.5:1Primary button on hover
Aa--text-on-color-softon--surface-accent4.51:1AAText, 4.5:1Ledes on red heroes
Aa--text-inverseon--surface-inverse19.67:1AAAText, 4.5:1White on black
Aa--text-inverseon--surface-inverse-raised17.04:1AAAText, 4.5:1White on raised black panels
Aa--text-inverse-mutedon--surface-inverse8.07:1AAAText, 4.5:1Grey text on black
Aa--text-inverse-mutedon--surface-inverse-raised6.99:1AAText, 4.5:1Grey text on raised black panels
Aa--text-accent-on-darkon--surface-inverse6.05:1AAText, 4.5:1Red text on black
Aa--text-accent-on-darkon--surface-inverse-raised5.24:1AAText, 4.5:1Red text on raised black panels
Aa--text-on-color-softon--surface-inverse15.85:1AAAText, 4.5:1Soft white on black
Aa--text-gold-on-darkon--surface-inverse7.41:1AAAText, 4.5:1Gold text on black
Aa--text-successon--surface5.26:1AAText, 4.5:1Success text
Aa--text-successon--surface-success4.65:1AAText, 4.5:1Success message
Aa--text-success-on-darkon--surface-inverse8.47:1AAAText, 4.5:1Success on black
Aa--text-success-on-darkon--surface-inverse-raised7.34:1AAAText, 4.5:1Success on raised black panels (Today card)
Aa--texton--surface-success16.45:1AAAText, 4.5:1Text in an all-clear panel
Aa--text-mutedon--surface-subtle5.92:1AAText, 4.5:1Empty-state text
Aa--sample-fgon--sample-bg13.93:1AAAText, 4.5:1Sample label
Aa--texton--surface-note17.44:1AAAText, 4.5:1Demo note
Aa--texton--highlight13.16:1AAAText, 4.5:1Prototype banner (yellow team note)
Aa--btn-inverse-fgon--btn-inverse-bg-hover17.89:1AAAText, 4.5:1Inverse button on hover
Aa--btn-outline-inverse-fg-hoveron--btn-outline-inverse-bg-active15.05:1AAAText, 4.5:1Outline-inverse button pressed
Aa--text-inverseon--btn-secondary-bg-active14.13:1AAAText, 4.5:1Secondary button pressed
Aa--tab-fgon--tab-bg6.40:1AAText, 4.5:1Unselected tab
Aa--scorebug-mutedon--scorebug-bg8.07:1AAAText, 4.5:1Score bug secondary text
--border-inputon--surface3.32:1PassUI, 3:1Field borders on white
--focuson--surface4.63:1PassUI, 3:1Focus ring on white
--focuson--bg4.21:1PassUI, 3:1Focus ring on the page
--focus-on-darkon--surface-inverse13.93:1PassUI, 3:1Focus ring on black
--focus-on-darkon--surface-accent3.79:1PassUI, 3:1Focus ring on red
--accenton--surface5.35:1PassUI, 3:1Red icons and rules on white
--accent-on-darkon--surface-inverse6.05:1PassUI, 3:1Red icons and rules on black
Aa--band-gold-texton--card-face12.06:1AAAText, 4.5:1Rank chip on a gold player card
Aa--band-silveron--card-face11.71:1AAAText, 4.5:1Rank chip on a silver player card
--band-redon--card-face5.24:1PassUI, 3:1Red frame on a top 10 player card
--band-goldon--card-face7.10:1PassUI, 3:1Gold hairline on a player card
--band-graphiteon--card-face5.13:1PassUI, 3:1Graphite frame on a player card
Aa--medal-inkon--medal-gold8.20:1AAAText, 4.5:1Glyph on an earned honour medal
Aa--medal-locked-texton--card-face6.99:1AAText, 4.5:1Glyph and next line on a locked medal
Aa--rink-inkon--rink-ice17.26:1AAAText, 4.5:1Numbers printed on the ice
--rink-uson--rink-ice4.97:1PassUI, 3:1Senators events on the rink
--rink-themon--rink-ice5.94:1PassUI, 3:1Opponent events on the rink
Aa--text-accent-strongon--bg7.26:1AAAText, 4.5:1Links on hover (--link-color-hover), light page
Aa--text-accent-strongon--surface7.98:1AAAText, 4.5:1Links on hover (--link-color-hover), light page
Aa--accent-activeon--bg10.49:1AAAText, 4.5:1Links while pressed (--link-color-active), light page
Aa--accent-activeon--surface11.54:1AAAText, 4.5:1Links while pressed (--link-color-active), light page
--focus-on-darkon--surface-inverse-raised12.06:1PassUI, 3:1Focus ring (--focus-ring-color), dark context

Typography

Two families. A condensed display face for the broadcast voice, and a clean sans for anything read in sentences. Sizes are fluid: they grow with the screen between a phone and a desktop size.

Barlow Condensed

--font-display, weights 500 to 900 plus italics. Headlines, nav, labels, buttons, numbers. Narrow letters fit big headlines on a phone, and the italic 900 is the score-graphic voice.

Inter

--font-sans. Body text, ledes, forms, captions. Open shapes and tall lowercase stay readable at 12px and in long articles, and it stays out of the way of the display face.

Fallbacks: --font-display and --font-sans fall back to system fonts, so text shows immediately while the web fonts load.

Type roles

Size, line height, tracking and weight travel together as a role. Use the .type-<role> class or the --type-<role>-* tokens.

  • Display

    .type-display

    44 to 84px · line 0.95 · tracking -0.005em · weight 900

     

    Page heroes (PageHero). One per page.

    Home opener Oct 8

  • Title

    .type-title

    36 to 60px · line 0.95 · tracking 0 · weight 900

     

    Article titles (ArticleView) and the lead story card.

    Chabot named captain

  • Headline

    .type-headline

    30 to 44px · line 1 · tracking 0.005em · weight 900

     

    Section titles (SectionHeader).

    Latest episodes

  • Subhead

    .type-subhead

    24 to 32px · line 1.1 · tracking 0.01em · weight 800

     

    Story card headlines (ArticleCard), article h2.

    Live from the Carp Fair

  • Card title

    .type-title-sm

    20 to 24px · line 1.15 · tracking 0.01em · weight 800

     

    Compact card headlines, article h3.

    The week ahead

  • Article body

    .type-body-lg

    18px · line 1.7 · tracking 0 · weight 400

     

    Article paragraphs.

    Article paragraphs are 18px with generous line height so they read easily on a phone.

  • Body

    .type-body

    16px · line 1.6 · tracking 0 · weight 400

     

    Everything else you read in sentences.

    Ledes, card summaries, form help.

  • Label

    .type-label

    14px · line 1.2 · tracking 0.08em · weight 700

     

    Eyebrows (.eyebrow), tags, buttons, nav.

    Full episode

  • Caption

    .type-caption

    12px · line 1.4 · tracking 0.01em · weight 500

     

    Photo credits, fine print.

    Photo: Coming In Hot. 4 min read.

Size scale

The raw steps behind the roles. Reach for a role first.

  • --fs-xs12px
  • --fs-sm14px
  • --fs-base16px
  • --fs-md18px
  • --fs-lg20 to 24px
  • --fs-xl24 to 32px
  • --fs-2xl30 to 44px
  • --fs-3xl36 to 60px
  • --fs-4xl44 to 84px
  • --fs-numeral64 to 128px, jersey numerals

Spacing and layout

A 4px base. Space is the cheapest way to make a page feel premium, so be generous between sections and tight inside a card.

Space scale

  • --space-14px
  • --space-28px
  • --space-312px
  • --space-416px
  • --space-520px
  • --space-624px
  • --space-832px
  • --space-1040px
  • --space-1248px
  • --space-1664px
  • --space-2080px
  • --section-gap40 to 72px between page sections

Containers and grid

Pages sit in a container with a fluid gutter. Grids go 1 column on phones, 2 from 640px, then 3 or 4 from 960px.

--container 1240px
--container-narrow 760px
--container-wide 1440px
.grid-4 · 1
.grid-4 · 2
.grid-4 · 3
.grid-4 · 4
Layout tokens
TokenPoints toValueUse
--containerBase value1240pxDefault page column
--container-narrowBase value760pxArticle column
--container-wideBase value1440pxAdmin and wide grids
--gutterBase valueclamp(1rem, 0.6rem + 1.6vw, 2rem)16px on phones, 32px on desktop
--header-hBase value64pxThe masthead bar
--banner-hBase value40pxPrototype banner pinned above the header while this is a demo (0px removes it)
--header-totalcalc(var(--header-h) + 35px + var(--banner-h))calc(64px + 35px + 40px)Everything sticky at the top (banner + top strip + bar + red rule). Use for sticky offsets.
--tabbar-hBase value60pxPhone tab bar height, not counting the iPhone home-indicator inset
--tap-targetBase value44pxSmallest thing a thumb has to hit
--app-header-height--header-totalcalc(64px + 35px + 40px)The sticky header as measured (SiteHeader writes the live value on <html>); the docked player stays below it
--dock-clearanceBase value0pxRoom the docked video player needs at the foot of the page (the player writes it on <html> while docked; the footer ends with a spacer this tall)

At (max-width: 760px): Phones: the header drops its top strip (the tab bar and More sheet carry those links), so the sticky header is the banner, the bar and its red rule (68px plus the banner).

Breakpoints

Mobile first: styles start at phone size and add columns as the screen grows. CSS can't read variables inside @media, so queries use these numbers directly.

Breakpoint tokens
TokenPoints toValueUse
--bp-smBase value480pxLarge phones
--bp-mdBase value640pxTwo-column grids start
--bp-phoneBase value760pxPhone chrome: at this width and under the tab bar replaces the top strip and Menu button
--bp-lgBase value960pxDesktop nav, three and four columns
--bp-xlBase value1200pxWide desktop

Radius, elevation and layers

Sports media is sharp: small radii, flat surfaces, and shadows only when something lifts or floats.

Radius

  • --radius-xs2px. Tags, focus rings
  • --radius-sm4px. Buttons, photos, fields
  • --radius-md6px. Cards
  • --radius-lg10px. Dialogs
  • --radius-pill999px. Filter chips, the Write pill

Elevation

  • --shadow-smLevel 1: resting controls
  • --shadow-mdLevel 2: hovered cards
  • --shadow-lgLevel 3: dialogs and pop-ups
  • --shadow-redPrimary button hover glow
  • --shadow-bar-upBars pinned to the bottom of the screen
  • --shadow-playPlay buttons over thumbnails
  • --shadow-hardFlat broadcast drop shadow on cut-outs
  • --shadow-inset-hairlineKeeps photos and avatars off same-colour backgrounds
  • --shadow-focus-haloFocused fields

Layers (z-index)

Pick a named layer. Never type a z-index number.

  1. --z-transition200Full-screen page transitions, above everything
  2. --z-tour160The guided tour (its dim, light and coach card): over dialogs, sheets and the full size player, under page transitions
  3. --z-cinema150The video player opened full size, and its backdrop: over dialogs and sheets, under page transitions
  4. --z-overlay100Dialogs, the More sheet, skip link
  5. --z-menu60The phone menu and the phone tab bar
  6. --z-dock55The docked video player: over the page and the masthead, under the phone menu, tab bar and every overlay
  7. --z-header50The site masthead
  8. --z-sticky4Sticky toolbars and save bars
  9. --z-raised1Lifted inside a card
  10. --z-below-1Decoration behind content

Motion

Quick and decisive, like a broadcast cut. Most things move in 120 to 200ms. Only big one-off reveals take longer, and everything respects reduced motion.

  • --easeStandard: quick start, soft landing
  • --ease-outThings arriving on screen
  • --ease-inThings leaving the screen
  • --ease-snapBroadcast wipe: hard in, hard out

With reduced motion switched on in the device settings, these jump straight to the end. The site does the same everywhere.

Motion tokens
TokenPoints toValueUse
--dur-instantBase value80msColour changes on press
--dur-fastBase value120msHover colour, press nudge
--durBase value200msMost transitions
--dur-slowBase value320msPanels and drawers
--dur-slowerBase value600msBroadcast wipes, one-off reveals
--dur-pulseBase value1600msThe live dot, loading shimmer
--easeBase valuecubic-bezier(0.2, 0.7, 0.2, 1)Standard: quick start, soft landing
--ease-outBase valuecubic-bezier(0.16, 1, 0.3, 1)Things arriving on screen
--ease-inBase valuecubic-bezier(0.7, 0, 0.84, 0)Things leaving the screen
--ease-snapBase valuecubic-bezier(0.7, 0, 0.2, 1)Broadcast wipe: hard in, hard out

Page transitions live in components/transitions/ and sit on --z-transition. See them all at the transitions lab.

Iconography

One set of simple line icons on a 24px grid, 2px stroke, square ends. They inherit the text colour. Icons support a label; they never replace one, except for universal controls (close, menu, play).

  • play
  • arrow-right
  • arrow-left
  • external
  • menu
  • close
  • calendar
  • pin
  • clock
  • mic
  • bag
  • heart
  • car
  • pen
  • tv
  • headphones
  • check
  • info
  • flame
  • home
  • newspaper
  • play-box
  • more
  • swatch
  • sparkle
  • plus
  • mail
  • lock
  • alert
  • image
  • printer
  • chevron-down
  • download
  • folder
  • chevron-left
  • chevron-right
  • pause
  • skip-back
  • skip-forward
  • volume
  • volume-off
  • maximize
  • minimize
  • resize
  • shuffle
  • share
  • arrow-up
  • arrow-up-right
  • youtube
  • podcast
  • instagram
  • x
  • tiktok
  • facebook
  • threads
  • discord
Icon props
name
56 names, aboveRequired.
size
20 (px)16 in small buttons, 18 to 20 inline, 24 for standalone controls.
label
stringPass it when the icon stands alone and means something. Otherwise it is hidden from screen readers.
strokeWidth
23 for tiny check and close marks.

Motifs

The NHL-broadcast energy, made from our own parts: speed slashes, score bugs, jersey numerals, ice and the red line. No league or team logos, wordmarks, crests or jersey designs, ever.

SpeedSlash

<SpeedSlash variant tone side wipe />

Game night

bars, red on black (hero)

Harvest Fundraiser

texture, shade on red

Latest episodes

mark, before a label

Use for

  • Page heroes and the masthead edge
  • Red or black feature bands (texture variant)
  • A small mark before a label (mark variant)

Not for

  • Behind reading text or over photos
  • More than one bars slash in the same screenful
  • As a stand-in for a team logo

ScoreBug

<ScoreBug label primary secondary tone live size />
OpenerOTT at TORSat, Oct 3
ink, the default
LiveComing In HotterPost-game show
red, live
Home openerPHI at OTTThu, Oct 8
sm, on a card

Use for

  • Real facts: next game, show times, episode numbers, Live
  • Lower-third labels on video and event cards

Not for

  • Invented scores or stats
  • Long sentences (two short lines at most)
  • Sponsor names (use SponsorSlot)

JerseyNumeral

<JerseyNumeral value label tone size filled shadow />
3
lg outline, light (hero)
12
md, dark
sm filled, steps

Use for

  • Ranks, counts, steps, episode numbers
  • One big numeral as a section hero

Not for

  • A real player name next to a number
  • Copying any team’s actual jersey lettering
  • Body text or prices

Player art

<PlayerArt number frame size />

The art on every player card, chip and sheet is the red alternate sweater seen from the back, the number big across it (Brent’s pick, 2026-10-20). On a card with an NHL head photo the sweater steps back, dimmed and to the right, so the whole number clears his head. Shown at the sizes the site draws it: the phone grid (173px), the desktop grid (260px), the player sheet, and the chips in leader rows, lines and goal cards.

card, phone grid, no photo
card, phone grid, NHL head photo
card, desktop grid, NHL head photo
sheet, no photo, widest pair
mini, 48px and 40px chips

IceTexture

<IceTexture as line padded />

Ice, plain

Next live show

Ice with the centre line

Mugmobile sightings

Use for

  • Light feature panels: events, stats, the Studio sign-in
  • Behind short labels and forms

Not for

  • Under long articles
  • Under photos or video
  • On black bands

BoardsRule

<BoardsRule label tone />
with a faceoff dot, and with a label
on black

Use for

  • Between major parts of a long page, at most twice
  • Before a footer-style wrap-up block

Not for

  • Ordinary section breaks (use SectionHeader)
  • Inside cards
LiveComing In Hot
DoBuild the energy from our own shapes: slashes, condensed italics, score-bug chips, red, black and white.
Don'tUse NHL or Senators logos, wordmarks, crests or copied jersey designs as our design elements. CIH's own logo and thumbnails are fine.

Components

Every shared component, live, in every variant and state. States are the same everywhere: hover, pressed, focus-visible, selected, disabled and loading, all from tokens.

Button

Minimum height 44px. One primary per view. Links get the same look; external links add an icon and say they open a new tab.

Primary. The one main action: Shop the drop, Publish.

  • Rest
  • Hover
  • Pressed
  • Focus
  • Selected
  • Disabled
  • Loading

Secondary. Strong but second: Watch, See the schedule.

  • Rest
  • Hover
  • Pressed
  • Focus
  • Selected
  • Disabled
  • Loading

Outline. Quiet actions next to a primary.

  • Rest
  • Hover
  • Pressed
  • Focus
  • Selected
  • Disabled
  • Loading

Ghost. Low-key text actions: Cancel, See all.

  • Rest
  • Hover
  • Pressed
  • Focus
  • Selected
  • Disabled
  • Loading

Inverse. Primary action on black or red bands.

  • Rest
  • Hover
  • Pressed
  • Focus
  • Selected
  • Disabled
  • Loading

Outline inverse. Second action on black or red bands.

  • Rest
  • Hover
  • Pressed
  • Focus
  • Selected
  • Disabled
  • Loading
External (opens in a new tab)
Sizes, block, icons, and two live demos: a toggle and a loading save
Button props
variant
'primary' | 'secondary' | 'outline' | 'ghost' | 'inverse' | 'outline-inverse'Default primary.
size
'sm' | 'md' | 'lg'sm is 36px: only in dense toolbars.
href / external
string / boolRenders a link. http links open a new tab.
icon / iconRight
Icon name
block
boolFull width.
loading
boolSpinner, aria-busy, no clicks. Keep the label.
selected
boolToggle buttons: sets aria-pressed.
disabled
boolButtons are disabled; links become inert with aria-disabled.

Links

Text links outside articles use .text-link. Inside .prose, links are styled automatically. Every state reads a context token (--link-color, --link-color-hover, --link-color-active), so on black and red bands the whole set switches together.

Tag and Sample label

Small uppercase chips. Plain labels by default; with href or onClick they become links or toggles with full states and a 44px tap area.

RedBlackOutlineMutedLiveSampleMedium
Tones
WhiteRedLiveSample
On black

Interactive tag (filter chip)

Live: a toggle group
Tag props
tone
'red' | 'black' | 'white' | 'outline' | 'muted' | 'live' | 'sample''live' adds a pulsing dot.
size
'sm' | 'md'
href / onClick
string / functionMakes the tag a link or a toggle.
selected
boolaria-current on links, aria-pressed on toggles.
SampleLabel
sizeRequired on every piece of demo content.

Card

The base surface. Feature cards (article, video, product, event) are built on the same look.

Light

The default surface

Dark

For video and stats

Red

Campaigns only

Interactive card (href)

Card props
tone
'light' | 'dark' | 'red'
href
stringThe whole card links. Turns on interactive.
interactive
boolHover lift, pressed, focus ring.
selected
boolRed frame for the chosen card in a set.
padded / as
bool / element

Form fields

48px tall, 16px text (so phones never zoom), a 3:1 border on white, a blue focus halo, and errors in words, not just red.

  • Hints sit under the field.

File input

  • Empty
  • Hover
  • Focus
  • Chosen
  • Invalid
  • Disabled
Field, Input, TextArea, Select, FileInput props
Field: id, label, hint, error, optional
Wires the label, hint and error to the control.
Input / TextArea / Select: invalid, error, hint
bool / stringSets aria-invalid and aria-describedby.
FileInput: id, label, note, fileName, invalid, disabled
A dashed drop zone around a real file input.

Tabs and navigation

Segmented tabs switch views of the same thing. Underline tabs switch sibling pages. The masthead at the top of this page and the phone tab bar are the live site navigation: white links, a white bar under the current page, gold focus rings on black.

Segmented (live)
Underline (live)

Tab states

  • Selected, hover, pressed
  • Disabled
Tabs props
items
[{ id, label, icon?, href?, disabled? }]href makes link tabs (aria-current="page").
value / onChange
id / functionButton tabs use aria-pressed.
variant
'segmented' | 'underline'
label
stringAccessible name of the group.

Brand and content parts

Coming In HotComing In HotComing In Hot
Wordmark (CIH's own logo): size sm, md, lg; tone light or dark; variant horizontal, square or mark; label sets the alt text
Coming In HotComing In Hot
Wordmark on black: square and round mark
Avatar: sm, md, lg, xl (initials on red without a photo)
PlaceholderArt: red and black

From the writers

Latest articles

See all
SectionHeader

Demo mode: posts you publish here are saved in this browser only. In the real build they save to your own database.

DemoNote: required on admin pages
AdSlot: rectangle (one or two per page, always labelled)

Presented by:Your partner here

Presented by:Your partner here

SponsorSlot: light and dark

Full props for every component, including the feature cards, are in DEVELOPERS.md.

Patterns

How the parts combine into the blocks every page is made of. These are the real components with real feed data.

Page hero

Every section index opens with PageHero. Show pages pass their show accent.

Watch

Episodes and clips

Full episodes, practice clips and post-game reaction from the Coming In Hot crew.

Article card grid

A .grid-3 of ArticleCards. The lead story uses the lead variant above it on the home page.

NewsComing In HotSample

Thomas Chabot named captain

The Coming In Hot crew broke down the captaincy news on the show. This is where the written take runs.

Video rail

Practice and post-practice clips in a .rail: swipe on phones, columns on desktop.

Clip

Stutzle's Superstar Status

October 20, 2026 · 224 views · 0:45

Clip

Brady's return is here

October 20, 2026 · 723 views · 1:16

Clip

Ullmark's early success

October 19, 2026 · 296 views · 0:45

Clip

The Teams Offensive Struggles

October 19, 2026 · 873 views · 0:44

Next game strip

Black strip from the public NHL schedule. Team names as text, never logos.

Screens

Whole screens are part of the system too, so the writer tools feel like the same brand as the site.

Studio sign-in

Preview: in the real build the team signs in here with email and a password, or a link sent to their email. Not active in the demo.

Sign in

or

You get access when Brent invites you.

Sign in

Sign in

Enter your email and we will send you a link that signs you in.

or

You get access when Brent invites you.

Email link

Sign in

or

You get access when Brent invites you.

Error

Check your email

We sent a sign-in link to graeme@example.com. Open it on this device and you are in. The link works once.

Preview only: no email was sent.

Check your email

Set your password

Choose a password for next time. An email link still works whenever you want one.

At least 10 characters. Not your email, and not the preview site password.

Not now

Signed in as graeme@example.com.

Preview only: nothing is saved.

Set your password
Open the full sign-in preview

Studio kit

The signed-in back end, in the same broadcast look: a black page band, panels with a black head and a red rule, score-bug tiles and roster rows. The rules are in DEVELOPERS.md, under Studio style guide.

Studio

My posts

Write or paste your story, add a photo, and publish. Everything saves as you type.

Identity

How your name reads

Sample
  • Visitors

    1,204 (sample number)

    Unique each day, added up

  • Read through

    64% (sample number)

    Reached 75% of the story

  • Sign-ups

    – (sample number)

    A dash when a number is not known

Nothing scheduled

Pick a date and time when you publish, and the post goes live on its own.

Admin editor

Where writers paste from Google Docs or Word, add a photo, pick an author and show, preview and publish. Shown live and scaled down.

Open the editor

Voice and copy

The words are part of the brand. These rules apply to every page, post, button and commit.

  • No em dashes. Ever.

    Use a full stop, a comma or a colon. Feeds and pasted posts are cleaned automatically.

  • Label every sample.

    Demo articles and events carry the yellow Sample label until real content replaces them.

  • Never "the official Senators podcast".

    Coming In Hot is independent. "Award-winning" is fine; it is the show’s own claim.

  • No invented facts.

    No made-up quotes, stats, injuries or trades about real people. Sample body copy says it is a sample.

  • Ads say Ad.

    Ad and sponsor slots stay generic placeholders until a partner signs. No third-party logos.

  • Talk like the show.

    Short sentences, plain words, Ottawa first. "Watch the full episode", not "Explore our content offerings".

Do“Watch the full episode.” Plain verbs, short, specific.
Don't“The official Ottawa Senators podcast.” Coming In Hot is independent.

Accessibility

Most Sens fans will open this on a phone, outside, one-handed. Accessible is the same thing as usable.

  • Contrast

    Body text meets 4.5:1 and UI parts 3:1. 57 of 57 declared pairs pass, and the build fails if one drops. See the pairs.

  • Focus you can see

    A 3px ring on every interactive part: blue on light, gold on black and red. Tab through this page to try it.

  • Tap targets: 44px

    Every button, tab and interactive tag is at least 44px tall. Small chips get an invisible larger hit area.

  • Reduced motion

    With reduced motion on, animations and transitions cut to near zero, and page transitions become a short crossfade.

  • Words for everything

    Alt text on photos, labels on every field, errors in words, icon-only buttons with a label, and external links that say they open a new tab.

  • Readable sizes

    Reading text is 16 to 18px with generous line height. Nothing goes below 12px, and fields use 16px so phones never zoom on focus.

Source of truth: lib/tokens.mjs. Guard: npm run check:tokens (runs before every build). Developer notes: DEVELOPERS.md.