Type
The split between the two type families is semantic, not decorative.
Source Sans 3 carries everything a person wrote. Source Code Pro
carries everything the machine reads, writes or names, at every size: tool
names, arguments, paths, versions, CLI fragments, headings included.
--font-size-body, packages/frontend/src/tokens/colors.css,
make verify. Never title case, never prettified.
Both families ship under packages/frontend/fonts/, not from a font host.
So a design renders in the right type behind a strict content policy or with
no network. make fonts generates them from @fontsource, and git keeps
them because the frontend package publishes them. A fresh clone and a
consumer have the same faces without a setup step.
Each upright family is one variable face across the weight axis. Source Sans
also ships its variable italic. The page preloads the upright latin faces, and
font-display: optional keeps a late response from a swap after paint. The
first visit on a slow connection can stay in the fallback. Later visits use
the cached face without a layout change.
Weight binds to a role too. Text, labels, headings and emphasis select named
--weight-* tokens, never a literal. Italic marks semantic emphasis and a
citation, not another heading style.
One scale, bound by role#
tokens/typography.css holds every size and nothing else. Each step has
the name of the register it is, never of the component that uses it.
tokens/controls.css holds the roles: --control-font-size,
--nav-font-size, --table-head-size. Each binds to a step above
without a second value. A size a component needs is one the scale names, or
the scale gains it.
Size tokens live under --font-size-*. --text-* is text colour. So a
token's name says if it changes the register or the ink.
The role is the durable decision. A literal on a button is a second scale with no relation to prose. A role bound to a step lets the scale move without a control left behind.
Font-size tokens use whole pixels. A half-pixel step is not a role, and it
rasterises less predictably. An em adjustment for optical alignment is a
ratio of its context, not a size in the scale.
Scale#
Two sizes carry text a person reads: --font-size-body is normal and
--font-size-small is small. There is nothing under them. Not a table
head, not a caption, not the closing line of a footer, not the annotation on
a specimen card. Only a reader who already knows the text can read a smaller
size.
The box and the face tell a button label from a nav item, or a table head
from the meta beside it. Never a pixel of size. The roles that once held a
step each are names in controls.css now.
A run of text that *is* the thing takes the reading size. One that is *about* something else takes the small one. So a control takes 16. A reader reads a button before a press, and Safari zooms the page at a smaller field on focus. So do a table's rows and heads, a rail, a breadcrumb, a pager, a set of tabs and a swatch's value. So does the error under a control: a hint describes the answer, an error stands between the reader and the send.
A caption, a hint, a footnote, an attribution and a badge are about the thing beside them, and stay small. So does the annotation on a specimen card.
No step stands between the two. A single pixel is a rasterisation difference, not a register a reader can tell apart.
Leading is a scale too#
--leading-* runs from closed to open: solid, display, heading, tight,
snug, body, code. Every role binds to one of them, the same way the sizes do.
Two steps carry a load. --leading-tight is what a table row's three
densities come from. --leading-heading is also what a control sets in: a
control does not wrap, and prose leading makes its height a function of the
body scale.
--leading-solid is the closed end, and it is for the button. A button is
a row of a glyph and a label. At the heading step the label's line box stands
taller than the mark beside it. Solid, the line box is the type size and the
row is one height. It is a step and not a bare 1, because that is what
the scale is for.
Prose runs at --leading-body. A block the machine wrote stays the most
open step, and a diff shares it. A reader reads both line by line.
Titles and bodies move together#
A titled component chooses a register, not two sizes. A note, a surface, a modal and an accordion use the block register. A card and a result use the entry register, because their titles are destinations. Each register binds a title token and the shared body token, so the pair moves as one decision.
A sentence-bearing block uses the reading register wherever it stands. A box does not make a note dense. Code, compact rows and captions choose a dense role, because a reader scans them as machine content. Density follows how a reader reads the content, not if it has a border.
So a component never sets --font-size-* on one half of a titled block.
That makes a second relation the register tokens cannot keep.
Display and headings#
Headings use sentence case. Writing carries the rest of the voice.
A heading sets itself. h1 to h3 carry the size of their level, so a
plain HTML outline needs no class. Below h3 everything is the UI
register. sds-h1, sds-h2 and sds-h3 are for the page that
disagrees with the outline. A section can stand third and read as the first
thing on the page. The level is the document's, the size is the page's.
sds-display is the opening line of a page, not a step in the outline.
Body and lead#
Lists#
A list indents by the width of its own marker, so an item's text lines up
with the paragraph above it. The marker takes the muted ink: it is
punctuation, not content. The element carries all of that. A <ul> from
an editor needs no class.
Two classes say what the element cannot. sds-list puts air between items
that are a sentence or two each. sds-list--plain takes the markers and
the top-level indent off a list of links.
sds-checks around a list gives each item a check, actions-check of the
icon set. The check takes the ink of the text. tone="ok" draws it in
--status-ok, for a list of what a reader gets. Use icons here, not emoji and
not a character.
An ordered list is the right shape for steps of one line each. A step with a command, a file to edit and a line that says it worked is an instruction: sds-steps, numbered stops down one rail, each with blocks.
The system does not decide which marker an ordered list counts with. A
source that said a. or i. arrives as HTML's type attribute.
The rules here leave that attribute alone.
The system does not decide which marker an ordered list counts with. A
source that said a. or i. arrives as HTML's type attribute.
The rules here leave that attribute alone.
Notes at the foot#
A statement with a source carries a mark, and the note stands at the foot of the page. The label hangs beside the note, so a stack of them lines up on one edge and an eye finds the one it wants.
sds-footnote is the block, and a citation is one of them. Its label
carries a work's name instead of a number. A page that needs the two apart
has the renderer's anchor: citation-<name> against footnote-<n>.
Arrival is a state. A browser scrolls to one row of a stack that all look
alike, so :target lights the label in --text-accent-quiet. A
heading, a confval and a term make the same mark when a link lands.
Arrival is a state. A browser scrolls to one row of a stack that all look
alike, so :target lights the label in --text-accent-quiet. A
heading, a confval and a term make the same mark when a link lands.
Mono and labels#
Every size is a step of the scale. Text a person reads stands at
--font-size-body or --font-size-small. A heading stands at its own
step. Nothing stands between two steps or under the small one.
Mono is the one exception, and it takes em. Every glyph in Source Code
Pro has the same advance, so a run at the size beside it reads a step larger.
--font-size-mono corrects that against the size the run stands in. It is
--font-modifier-mono times 1em, and never less than the small step. In
body copy that is 14.4px. A step times the ratio is a second scale nobody
chose, and on the small step it falls under the floor.
tokens/fonts.css states the ratio once. A mono declaration that reaches a
size without it draws a step larger than the sans beside it. A mark on its own
is the other exception, and it takes em too. A permalink, a section number,
a list marker and a raised figure are characters. Their size is an optical
choice.
Inline code has no box and no colour of its own. A tinted, padded chip breaks the line at every occurrence, and a reference sentence can hold four. A second ink collides with the link or claims a status the run does not have.
A step of weight sets the run apart instead. It is --weight-medium where
the sentence is regular. That puts back the colour the ratio took out, at the
cost of no box and no hue. A block the machine wrote takes the step off
again. The block decides size, weight and colour, and a whole block in medium
reads as emphasis.
A name that is a link, or stands in one, keeps the link's ink. The face is
the machine's. The colour is what says the word is a link. The link's
rules say it at rest, under the pointer and on a tinted plane. The same
holds for a name cell in a table: the primary ink, never the link ink,
because --text-link says link and nothing else.