` renders at plain 400, which is unmistakably wrong, which is the point — it fails in review instead of in production. The fix is then always the same, load the file you actually asked for.
```tsx
html { font-synthesis: none; }
```
- [jakubkrehel — better-typography SKILL.md](https://raw.githubusercontent.com/jakubkrehel/skills/main/skills/better-typography/SKILL.md)
- [jakubkrehel — variable-fonts-and-opentype.md](https://raw.githubusercontent.com/jakubkrehel/skills/main/skills/better-typography/variable-fonts-and-opentype.md)
- [MDN — font-synthesis](https://developer.mozilla.org/en-US/docs/Web/CSS/font-synthesis)
### Properties Over Raw Font Tags
**ID:** `content-font-properties-over-tags`
**Agent rule (MUST):** Use the CSS PROPERTY whenever one exists — `font-weight: 650`, `font-optical-sizing: auto`, `font-variant-numeric: tabular-nums` — not `font-variation-settings: "wght" 650` / `font-feature-settings: "tnum" 1`. Raw-tag declarations address axes that only exist inside the variable file, so when a non-variable fallback renders they SILENTLY DO NOTHING (weight collapses to 400, no console error). And `font-feature-settings` is ONE property, so a later declaration CLOBBERS an earlier one — add `"ss01" 1` to a rule that carried `"tnum" 1` and the tabular figures leave with it. Reserve the raw tags for custom axes (`"GRAD" 80`) and niche features (`"ss01" 1`) that have no property of their own.
**Source:** jakubkrehel (https://github.com/jakubkrehel/skills)
Reach for font-weight and font-variant-numeric, not font-variation-settings and font-feature-settings
> When a CSS property exists, use it. `font-weight: 650` instead of `font-variation-settings: "wght" 650`, `font-optical-sizing: auto` instead of `"opsz"`, `font-variant-numeric: tabular-nums` instead of `font-feature-settings: "tnum" 1`. Properties keep working when a non-variable fallback renders. Reserve the raw-tag properties for custom axes (`"GRAD" 80`) and niche features (`"ss01" 1`) that have no property of their own.
This is an API rule, not a typography rule, and it is the one people get wrong precisely because both spellings look equivalent while the variable font is loading fine. They are not. `font-variation-settings: "wght" 650` addresses an axis that only exists inside a variable file, so the moment the fallback stack renders — the file 404s, the CDN is blocked, the user is on a metered connection with fonts disabled — the declaration is not overridden, it is inert. The weight collapses to 400, the console says nothing, and the semibold heading you designed is now body copy. `font-weight: 650` is a real property that every font stack understands: the browser picks the closest available face and the hierarchy survives. The same asymmetry applies below the surface for features: `font-feature-settings` is a single low-level property rather than a set of independent switches, so the day someone adds `"ss01" 1` to a rule that already carried `"tnum" 1`, the tabular figures leave with it and the ticker starts jittering again. `font-variant-numeric: tabular-nums` cannot be clobbered that way, because it lives on its own property. Note this is orthogonal to content-tabular-numbers, which says *what* to apply and never *which API* — this is the rule that picks the API. Raw tags are not banned; they are the escape hatch, and the only correct use of them, for custom axes like `"GRAD" 80` and niche features like `"ss01" 1` that have no property of their own.
```tsx
/* ✗ inert under a non-variable fallback; "tnum" clobbered by the next rule */
.price { font-variation-settings: "wght" 650; font-feature-settings: "tnum" 1; }
/* ✓ real properties, survive the fallback, cannot clobber each other */
.price { font-weight: 650; font-variant-numeric: tabular-nums; font-feature-settings: "ss01" 1; }
```
- [jakubkrehel — better-typography SKILL.md](https://raw.githubusercontent.com/jakubkrehel/skills/main/skills/better-typography/SKILL.md)
- [jakubkrehel — variable-fonts-and-opentype.md](https://raw.githubusercontent.com/jakubkrehel/skills/main/skills/better-typography/variable-fonts-and-opentype.md)
- [MDN — font-variant-numeric](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-variant-numeric)
- [MDN — font-weight](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-weight)
### Heading Sizes Descend with Level
**ID:** `content-heading-size-descends`
**Agent rule (MUST):** Pick the heading tag from the document outline and the size from a DESCENDING step of the type scale — a lower level must never render larger than a higher one on the same page. Never reach for a tag because it "looks right". Adjacent levels may share a size at the small end if weight or spacing keeps them distinct; a line that must be the loudest thing on the card without owning a rank is a `` at display size. Distinct from `content-rams-heading-levels`, which is about SKIPPING levels (a11y outline) — here the tags are sequential and lint passes, but the render contradicts them.
**Source:** jakubkrehel (https://github.com/jakubkrehel/skills)
Map every heading level to a descending step of the scale so a lower level never renders larger than a higher one
> Map each heading level used on a page to a descending step of the type scale: a lower level must never render larger than a higher one on the same page. Adjacent levels may share a size toward the small end of the scale as long as weight or spacing keeps them distinct. Pick the tag from the document outline and control the size with CSS; never skip levels or reach for an `h4` because it "looks right".
This is a different failure from content-rams-heading-levels, and worth separating carefully. That rule is about SKIPPING levels — h1 straight to h3 — which lint catches and WCAG 1.3.1 names. This one is the inverse-SIZE failure, and every tag in it is sequential, so the lint passes: an `
` set at `text-base` because it looked right in the card, wrapping an `` at `text-2xl` because that was the line the designer wanted read first. The tags are legal; the render contradicts them. A screen reader navigates the outline and hears "Revenue up 12%" as subordinate to "Q3 report"; the page says the opposite, in twice the pixels. Whenever a child level renders larger than its parent, the tag was picked for its default size instead of from the document structure. Take the tag from the outline, take the size from a descending step of the scale, and when a line genuinely needs to be the loudest thing on the card without owning a rank, make it a `
` at display size — it can be as big as it likes and it stays out of the outline entirely.
- [jakubkrehel — better-typography SKILL.md](https://raw.githubusercontent.com/jakubkrehel/skills/main/skills/better-typography/SKILL.md)
- [jakubkrehel — spacing-and-sizing.md](https://raw.githubusercontent.com/jakubkrehel/skills/main/skills/better-typography/spacing-and-sizing.md)
- [MDN — Heading elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/Heading_Elements)
### Truncate Without Losing the Value
**ID:** `content-truncation-keeps-value`
**Agent rule (MUST):** If the text an ellipsis hides discriminates rather than decorates, keep the full value reachable — a `title` (which also lands in the accessible name), a tooltip, an expanded view, or middle truncation that pins the discriminating tail (`invoice-2026-…-final-v2.pdf`) and collapses the boring head. Hover-only tooltips are not enough on their own: keyboard and touch users never trigger them, so pair them with something focusable.
**Source:** jakubkrehel (https://github.com/jakubkrehel/skills)
If the text an ellipsis hides carries meaning, keep the full value reachable in a tooltip or an expanded view
> Truncation hides content, so if the missing text matters, keep the full value reachable in a tooltip or expanded view.
Two neighbouring rules already cover the mechanics and neither covers this. content-ibelick-text-overflow says to USE `truncate` or `line-clamp` in dense UI; layout-min-width-truncation makes truncation actually WORK inside a flex or grid child. Both are about the ellipsis appearing where it should. This rule is about what the ellipsis ate. A file column that renders `invoice-2026-q3-a…` on every row has not shortened three filenames, it has deleted the only part that told them apart, and the user is left choosing an invoice by its byte count. So: decide whether the hidden text is decoration or discrimination. If it discriminates, keep it reachable — a `title` (which also lands in the accessible name), a tooltip, a details view, or middle truncation that pins the tail (`invoice-2026-…-final-v2.pdf`) and collapses the boring head instead. Hover-only tooltips are not enough on their own, since keyboard and touch users never trigger them, so pair them with something focusable or an expand toggle.
- [jakubkrehel — better-typography SKILL.md](https://raw.githubusercontent.com/jakubkrehel/skills/main/skills/better-typography/SKILL.md)
- [jakubkrehel — wrapping-and-punctuation.md](https://raw.githubusercontent.com/jakubkrehel/skills/main/skills/better-typography/wrapping-and-punctuation.md)
- [MDN — text-overflow](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/text-overflow)
- [MDN — title attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/title)
### Tracking Is Size-Specific
**ID:** `content-tracking-is-size-specific`
**Agent rule (SHOULD):** Do not ship one `letter-spacing` for all sizes. Leave body near 0, tighten display type (~ -0.02em at 36px+), and give small caps/captions a small positive nudge. Prefer a variable font's optical-sizing axis (`font-optical-sizing: auto`) over a hardcoded value. This refines, not contradicts, "don't track body text".
**Source:** Emil Kowalski (https://emilkowalski.com/)
Tighten letter-spacing as display type grows and leave body near zero — a single tracking value is wrong at some size
> Tracking (letter-spacing) is size-specific — never one value for all sizes. Large display text wants negative tracking; small text wants slightly positive tracking for legibility. Tighten headings, leave body near 0.
From the typography section of Emil Kowalski's apple-design skill, and it sits in DELIBERATE TENSION with content-ibelick-letter-spacing, which says never touch tracking unless asked. The corpus keeps both, because they are right about different sizes. ibelick's rule protects body text, where a face was hinted for reading and any manual tracking almost always makes it worse — that is the common case and the safe default. Emil's rule is about the extremes a text-optimised face was never spaced for: at display sizes (36px and up) the designer-intended spacing reads too loose, the letters drift apart, and the headline stops cohering, so it wants slightly NEGATIVE tracking; at caption sizes (11–12px) it wants a touch POSITIVE to hold the glyphs apart. The honest reconciliation is that the best tool is not manual tracking at all but optical sizing — `font-optical-sizing: auto` on a variable font with an `opsz` axis corrects spacing, contrast and detail per size automatically, which is exactly what "type is designed with proper spacing" should mean end to end. Reach for a manual `letter-spacing` step only when the face has no `opsz` axis. So: never randomly track body (that is ibelick's rule intact), do correct display and caption, and prefer `opsz` over a hardcoded value. Related: content-impeccable-type-scale-contrast, content-heading-size-descends, content-leading-tracks-size (its line-height counterpart).
```tsx
h1 { font-size: 48px; letter-spacing: -0.02em; }
.caption { font-size: 11px; letter-spacing: 0.04em; }
body { font-optical-sizing: auto; }
```
- [Emil Kowalski — apple-design SKILL.md](https://raw.githubusercontent.com/emilkowalski/skills/main/skills/apple-design/SKILL.md)
- [MDN — font-optical-sizing](https://developer.mozilla.org/en-US/docs/Web/CSS/font-optical-sizing)
- [MDN — letter-spacing](https://developer.mozilla.org/en-US/docs/Web/CSS/letter-spacing)
### Leading Tightens as Size Grows
**ID:** `content-leading-tracks-size`
**Agent rule (SHOULD):** Set line-height inversely to font size — do not inherit one ratio everywhere. Large display type wants ~1.0–1.2 so wrapped lines cohere; body wants ~1.5–1.7. Give scripts with tall ascenders/descenders more, dense data UI less. Pair each type-scale step with its own ratio.
**Source:** Emil Kowalski (https://emilkowalski.com/)
Set line-height inversely to font size — tight on large headings, generous on body — instead of one ratio for everything
> Leading (line-height) tracks size inversely. Tight on large headings, looser on body copy. Increase it for scripts with tall ascenders/descenders; tighten it for dense, information-heavy UI.
From the typography section of Emil Kowalski's apple-design skill, and the complement to content-impeccable-tight-leading rather than a repeat of it. That rule sets the FLOOR — body copy wants 1.5 to 1.7 so lines do not crowd. This rule is about the slope: as size climbs, the ratio must come DOWN. The reason is that line-height is proportional, so a 1.5 that is perfect at 16px body becomes 72px of leading on a 48px headline — the two lines of a wrapped title drift so far apart they read as unrelated rows instead of one heading. Large display type wants roughly 1.0 to 1.2; a bare 1 is often right for a single-line hero. Because unitless line-height inherits as a multiplier, the practical implementation is a scale that pairs each size step with its own ratio, not one global `line-height` on `body`. Two edge cases from the source: scripts with tall ascenders and descenders (and any mixed-language UI) need MORE leading than Latin at the same size, and dense information UI (tables, compact lists) can go tighter than prose. Related: content-heading-size-descends, content-impeccable-type-scale-contrast, content-tracking-is-size-specific (its letter-spacing counterpart).
```tsx
h1 { font-size: 48px; line-height: 1.1; }
p { font-size: 16px; line-height: 1.6; }
```
- [Emil Kowalski — apple-design SKILL.md](https://raw.githubusercontent.com/emilkowalski/skills/main/skills/apple-design/SKILL.md)
- [MDN — line-height](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height)
### Legible Text on Translucent Surfaces
**ID:** `content-vibrant-text-on-translucent`
**Agent rule (SHOULD):** Text over a `backdrop-filter` surface faces a background whose luminance changes as content scrolls. Raise contrast and weight, nudge letter-spacing up slightly, and keep the opacity/color on a SOLID backing layer while the text itself stays fully opaque — never fade the text with the surface.
**Source:** Emil Kowalski (https://emilkowalski.com/)
Over a blurred or translucent surface, raise text contrast and weight and keep color on a solid layer — flat gray text vanishes as the backdrop shifts
> Vibrancy keeps text legible over changing backgrounds. Over blurred/translucent surfaces, don't use flat gray text — use higher-contrast, slightly heavier weight, and a small letter-spacing bump. Put color on a solid layer, not the translucent foreground.
From the vibrancy section of Emil Kowalski's apple-design skill, and it fills the gap between two rules the corpus already has about translucent surfaces. design-impeccable-no-glassmorphism decides WHEN glass is justified (only over content that genuinely scrolls behind it); design-reduced-transparency-contrast handles the accessibility fallback (go solid under prefers-reduced-transparency). Neither says how to make the text on the glass readable once you have decided to keep it — and that is the hard part, because a `backdrop-filter` surface shows whatever is behind it, so the luminance under any given word changes as the user scrolls. A muted gray tuned against one section of the page becomes unreadable over the next. Apple's answer is "vibrancy": foreground text that adapts to what it sits on. On the web you approximate it deliberately — raise the contrast (secondary text on glass should read closer to primary than it would on a solid card), add a touch of weight, and nudge letter-spacing up a hair so thin strokes survive the blur. The load-bearing detail is the last clause: keep any color and the opacity on a SOLID backing layer behind the text, and render the text itself fully opaque on top — never fade the text with the surface, or it dims exactly where the background is busiest. Related: content-impeccable-dark-mode-text-compensation (the same weight/spacing compensation for light-on-dark), design-minimum-contrast.
```tsx
.glass { background: rgba(20,18,28,0.55); backdrop-filter: blur(8px); }
.glass p { color: #fff; font-weight: 600; letter-spacing: 0.01em; }
```
- [Emil Kowalski — apple-design SKILL.md](https://raw.githubusercontent.com/emilkowalski/skills/main/skills/apple-design/SKILL.md)
- [Apple HIG — Materials (vibrancy)](https://developer.apple.com/design/human-interface-guidelines/materials)
- [MDN — backdrop-filter](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter)
### Canvas and WebGL Need an Accessible Fallback
**ID:** `content-canvas-accessible-fallback`
**Agent rule (MUST):** A