The rules a page must follow. Added 2026-07-28 after a review of the Social Media Planner found four different treatments doing the same job on one screen, and no written rule to point at.
Buttons, labels, headings, navigation, card titles and menu items are all sentence case. “Save draft”, not “Save Draft”. “Publish now”, not “Publish Now”.
Title Case is retired. There is no consistent rule about which words take a capital, so two authors produce two results, which is exactly how the planner ended up showing “Publish now” and “Publish Now” for the same action on the same screen.
The one exception is the eyebrow, which is uppercase. Nothing else is.
Two supporting reasons, both practical:
Never type capitals into markup. Write sentence case and apply
text-transform: uppercase in CSS. Some screen readers spell out genuinely
uppercase strings letter by letter. The eyebrow utility already does this
correctly.
Comedik is never uppercased. A hand-drawn face depends on its irregular lowercase for its character; capitals flatten it and read as shouting.
Each of these does one job. Pick by role, not by how big you want the text.
| Element | When | Treatment |
|---|---|---|
| Eyebrow | Where am I, above a headline | Uppercase, tracked, muted lavender, max ~3 words |
| Page headline | The page’s one h1 |
Bold ink, page-header component |
| Card title | Names a panel | Bold ink h3 element at the small step (14px/700), sentence case. Small deliberately: it names a panel rather than heading a page, and at the h3 size it competes with the page headline |
| Sub | One sentence of context | Muted, capped at 70 characters wide |
| Hint | Guidance attached to a control | Italic muted, small |
Sibling cards on a page must use the same header treatment. A card with a bold ink title beside a card with a muted uppercase label reads as two systems bolted together, and a card with no header at all beside cards that have one reads as unfinished.
Use real heading elements. A div styled to look like a heading gives
screen-reader users nothing to navigate by and silently removes the page
from the document outline.
Spacing comes from the 4px scale. The named steps below are the only ones a page should reach for.
| Use | Step |
|---|---|
| Inside a card | space-5 (24px) |
| Between cards | space-5 (24px) |
| Between a label and its control | space-2 (8px) |
| Between stacked text in a card | space-3 (12px) |
| Between major page sections | space-6 (32px) |
Hand-picking a value per page is how card padding across the estate ended up ranging from zero to 30px, and how one selector ended up set to both 24px and 12px in the same file.
Prefer a container that owns the gap (.ts-stack, .ts-pill-row, or grid
and flex gap) over margins on each child. Per-child margins collapse,
double up, and leave the spacing dependent on which element happens to be
first or last.
Pill and chip rows must set align-items: center. A flex row defaults to
stretch, which inflates a small chip to the height of the tallest pill
beside it. Use .ts-pill-row.
An empty card keeps its frame, its title and its place in the layout. Inside it, three things:
“No posts scheduled for this date.” satisfies none of these. It is a dead end at exactly the moment the user’s intent is clearest, since they have just selected the date.
Everything selectable uses the same treatment: brand border on the selected item. Mode cards, calendar day cells, platform tabs and toggles all share it.
Do not introduce a second selection language, such as a filled square with sharp corners in a system otherwise made of 12px cards and full pills.
Green is the full-auto action. Orange is secondary. Purple is everything else. Red is danger, and danger buttons additionally carry the hazard tape edge so that danger is distinguishable from secondary without relying on hue alone: red and orange are precisely the pair colour-blind users lose.
The most consequential action on a page must not be quieter than a lesser one beside it. Check this by role, not by taste.
Every colour that carries text ships as a background and foreground pair, and both of these must hold:
Checking only the first is how the old palette passed review and still failed in practice: buttons whose edges vanished against the page.
The extension puts controls inside YouTube’s own interface. Two rules, both learned the expensive way while injecting into their community composer.
Be compact by default. A host’s control row is usually a wrapping flex container, so every pixel added can reflow controls we do not own. An icon with a label matched YouTube’s rhythm perfectly and pushed their own Video option onto a second line. Breaking the host’s layout is a worse outcome than our button being unlabelled, so injected controls start icon-only and earn a label only where there is provably room.
Take colour from the host’s token, never inherit. Our marks fill with
currentColor, and inherit resolved to a value with no contrast against
YouTube’s surface, so the button rendered invisible while its own panel
rendered correctly. Pinning to the host’s variable, --yt-spec-text-primary
in that case, fixed it and follows their light and dark themes for free.
The general rule behind both: inside someone else’s page, their layout and their tokens are the environment. Ours apply to our own surfaces only, and an injected control that ignores the host is the one that breaks.