The two mechanisms are related but not interchangeable
The similar names obscure an important boundary. A declaration such as font-size-adjust: 0.52 belongs on an element and takes effect as the browser selects faces from that element's font stack. A declaration such as size-adjust: 102% belongs inside @font-face and changes the defined face itself.
| `font-size-adjust` property | `@font-face size-adjust` descriptor | |
|---|---|---|
| Where it is declared | On an element or inherited text style | Inside one @font-face rule |
| What it affects | The used size of selected fonts in that text's stack | Glyph outlines and metrics for the defined face |
| Primary input | A metric keyword and value, or a browser-derived value such as from-font where appropriate | A percentage multiplier derived for a specific source and target face |
| Best fit | Preserving x-height, cap height, or another supported metric as fallback selection changes | Making a named fallback face closer to a primary face before it enters the stack |
| Related controls | font-family, font-size, the selected metric, and the faces available to the browser | ascent-override, descent-override, line-gap-override, weight, style, and src |
| What it cannot prove | That the preferred file loaded, text wraps identically, or layout stayed still | That the chosen fallback exists, every requested face maps correctly, or the final swap avoids movement |
The property can target more than lowercase x-height. MDN documents metric keywords including ex-height, cap-height, ch-width, and ic-width. That flexibility matters because a legibility problem isn't necessarily a wrapping problem. Preserving x-height may make fallback body text easier to read while leaving the line breaks different.
Do not assume the effects add together
MDN notes that font-size-adjust calculations use overridden font metrics. If a face has already been changed with the size-adjust descriptor, the property calculation can compensate for those adjusted metrics and neutralize the descriptor's apparent scaling effect. Combining both mechanisms creates a new behavior to test, not two percentages to add.
Choose from the failure you can observe
Start with the rendered failure, not the CSS feature name. Your route depends on the problem: readability, horizontal measure, vertical metrics, loading behavior, or the fallback face itself.
- 1
Fallback text is too small or hard to read
Consider
font-size-adjustif preserving a supported metric across the stack addresses the problem. Test both the preferred and fallback states because the property affects font sizing, not only one temporary face. - 2
Fallback text wraps differently
First choose a fallback with reasonably similar width characteristics. Then evaluate an adjusted fallback face. Measure width and wrapping directly because matching x-height alone doesn't guarantee matching line breaks.
- 3
Text blocks have different vertical geometry
Inspect ascent, descent, and line gap. An adjusted
@font-facecan pairsize-adjustwith the three metric-override descriptors, but every percentage must come from the actual faces being compared. - 4
The page moves when the web font arrives
Compare the forced fallback with the loaded face, then inspect loading policy and resource timing separately. Metric overrides can reduce a geometric difference, but they don't decide whether or when a swap happens.
- 5
The fallback looks wrong even when its box is close
Choose another fallback. Scaling can't give one typeface another typeface's shapes, script coverage, texture, or character distinctions.
Metric matching is not visual cloning
Two faces can produce similar text boxes yet differ noticeably in stroke, spacing, punctuation, numerals, or script coverage. Decide whether your acceptance criterion is legibility, stable geometry, visual similarity, or a combination.
Recipe 1: preserve a metric across the stack
Use the property route when the requirement belongs to the text role, not one custom fallback face. This example is deliberately incomplete: 0.52 is a placeholder that you must replace with a value obtained for the real primary face. It isn't a recommendation for Plus Jakarta Sans or any other family.
.product-body {
font-family: "Primary Sans", "System Fallback", sans-serif;
font-size: 1rem;
font-size-adjust: ex-height 0.52; /* illustrative only */
}This rule tells the browser to preserve the specified aspect value for the selected metric. It doesn't assign a manually chosen CSS font-size to each fallback or define a new face. Keep the original text role's intended size and line height visible during testing so you can identify which declaration changed the result.
For an x-height decision, test representative lowercase text. Use uppercase labels for a cap-height decision and width-sensitive strings for a width decision. Include punctuation, numerals, controls, and every script the product supports. A paragraph of repeated Latin lowercase letters is too narrow a fixture for a product UI.
Recipe 2: define a metric-matched local fallback face
Use an adjusted face when a specific local font needs to stand in for a specific primary face. Give it a distinct family name. The project can then request the adjusted definition instead of bypassing it and selecting the raw local family.
@font-face {
font-family: "Primary Sans Fallback";
src: local("System Fallback");
size-adjust: 101.5%; /* illustrative only */
ascent-override: 91%; /* illustrative only */
descent-override: 24%; /* illustrative only */
line-gap-override: 0%; /* illustrative only */
font-weight: 400;
font-style: normal;
}
.product-body {
font-family: "Primary Sans", "Primary Sans Fallback", sans-serif;
}size-adjust scales outlines and metrics. The ascent, descent, and line-gap descriptors address the vertical proportions used for line layout. Tune them against the same primary face, weight, and style that the rule is meant to replace. A value derived from regular roman text doesn't automatically cover bold, italic, or a variable-font axis position.
Create separate face definitions when separate resources or measured variants require them. Make font-weight and font-style explicit. Otherwise, the browser may synthesize a requested face or select another available face, and you'll end up tuning numbers against the wrong comparison.
Keep an unadjusted control
Render the raw fallback, adjusted fallback, and loaded primary in the same fixture. Without the raw control, you can see that two samples differ, but you can't tell whether an override improved the intended metric.
Keep adjacent font decisions separate
Metric adjustment is only one part of a font implementation. It doesn't choose resources, loading behavior, or cascade mappings. Record those decisions separately so one layer's failure isn't hidden behind another metric tweak.
- Family selection: which fallback is acceptable for each role, script, and environment.
- Face mapping: which files or local faces serve each requested weight, style, stretch, and variable axis range.
- Synthesis policy: whether the browser may synthesize bold or italic when a requested face is absent.
- Loading policy: the
font-displayvalue and the resulting block and swap behavior. - Delivery: URLs, WOFF2 files, origin policy, subsetting, caching, and any justified preload.
- Metric adjustment: property values or face-specific descriptors derived from the selected resources.
- Rendered acceptance: observed glyphs, wrapping, line boxes, movement, and browser conditions.
Changing font-display can alter when the fallback and primary faces appear. Preloading can alter resource timing. Neither step makes two faces metrically compatible. The reverse is also true: a carefully adjusted fallback doesn't prove that the browser requested the primary resource successfully. Diagnose each stage separately.
Start from an explicit typography role
A design kit can establish the intended family, weights, scale, and usage rules before the consuming project chooses resources and fallback metrics. Use the public Ambient Sage typography system as bounded upstream input, then verify browser behavior inside your project.
Ambient Sage is upstream intake, not a browser result
Ambient Sage provides a concrete typography contract. Plus Jakarta Sans serves body and heading roles at weights 400, 500, 600, and 700. JetBrains Mono serves the mono role at weights 400, 500, and 700. Its published scale is compact-product. Those facts define what a consuming project is trying to implement, but they don't supply a universal system fallback, measured adjustment percentages, a loading policy, or cross-browser results.
Token specimen · real values
Ambient Sage
Live renderAmbient Sage's actual tokens — the same values its exports use.
Verify forced-fallback and loaded-font states
Generated CSS is evidence of a proposed implementation, not its effect. Test one named consumer in both font states. A high-reach body role, navigation label, or dense data row is a better starting point than an isolated alphabet sample because it exposes wrapping and neighboring layout changes.
- 1
Freeze the consumer and conditions
Name the route, component, text role, content fixture, viewport widths, browser versions, operating systems, zoom, and expected result. Record the current font files and CSS revision.
- 2
Confirm the requested face
Record the computed family, size, weight, style, stretch, line height, synthesis policy, and any
font-size-adjustvalue. A request for weight 600 isn't proof that a 600 face rendered. - 3
Inspect the actual resource
Use browser font and network inspection to identify the rendered face and determine whether the primary resource was requested, loaded, blocked, cached, or substituted. Preserve a screenshot or trace reference without treating it as evidence for other browsers.
- 4
Force fallback
Block the primary face or make it temporarily unavailable in the controlled test. Capture representative glyphs, line breaks, element dimensions, line boxes, overflow, truncation, and neighboring movement.
- 5
Restore the loaded face
Repeat the same fixture with the primary face confirmed as rendered. Compare the same measurements instead of relying on memory or a quick visual toggle.
- 6
Test the swap path
Observe whether and when the text changes under controlled loading conditions. Record layout movement separately from static similarity. A close final overlay doesn't prove that the loading transition was stable.
- 7
Repeat the variants that can diverge
Check required weights, italics, controls, numerals, scripts, and both narrow and wide viewports. Stop at the declared scope. One passing role doesn't approve the whole typography system.
| Record | What to capture | |
|---|---|---|
| Request | Requested family, weight, style, stretch, size, line height, and synthesis policy | Computed CSS plus the rule and revision that supplied it |
| Resource | Actual rendered face and primary resource status | Browser font inspection and network evidence |
| Forced fallback | Glyphs, wrapping, line count, line boxes, overflow, and element dimensions | Screenshot plus recorded measurements at named viewports |
| Loaded face | The same observations with the primary face confirmed | Matching screenshot and measurements |
| Swap behavior | Timing and visible movement during the transition | Controlled trace, layout-shift evidence, or recorded observation |
| Decision | Pass, revise, block, or unresolved within the named scope | Owner, correction layer, evidence link, and retest trigger |
A visual overlay is useful evidence, especially while tuning an adjusted fallback, but it remains incomplete. It may reveal outline or box differences in one fixture without showing which face rendered in the application, whether a resource failed, or whether a delayed swap moved the surrounding content.
Find the first divergent layer
When the result fails, correct the earliest layer that differs from the recorded expectation. Tuning another percentage at the end of the chain may hide the symptom while leaving the wrong face or resource mapping in place.
- Typography source: the intended family, role, weight, style, or acceptance criterion is missing or disputed.
- Font resource: the expected file or local face is absent, blocked, mislabeled, or different from the measured source.
- Emitted CSS: the intended property, descriptor, or face definition is missing or stale.
- Project mapping: the role points to the wrong family name, weight, style, variable range, or fallback order.
- Component override: local CSS changes the family, size, line height, letter spacing, or synthesis behavior.
- Rendered observation: the expected face renders, but wrapping, line boxes, legibility, or movement still misses the acceptance criterion.
A layout-shift clue is not a root cause
Layout-shift attribution can identify the element that moved without identifying the upstream element or font decision that caused the movement. Confirm the font state and compare line breaks before assigning the fix.
Copy the acceptance record
Write the expectations before testing and the observations afterward. This keeps a plausible screenshot from quietly changing the success criterion.
Font fallback acceptance record
Consumer: [route / component / text role]
Revision: [application and CSS revision]
Environment: [browser, version, OS, viewport, zoom]
Content fixture: [exact representative strings or fixture ID]
Requested family: [value]
Requested weight / style: [value]
Actual primary resource: [file or unresolved]
Actual fallback face: [face or unresolved]
Metric mechanism: [font-size-adjust / adjusted @font-face / neither]
Adjustment source: [measurement method and source/target face versions]
Expected forced fallback:
- representative glyphs: [criterion]
- wrapping and line count: [criterion]
- line boxes and overflow: [criterion]
Observed forced fallback: [unrun / observation]
Evidence: [reference]
Expected loaded face: [criterion]
Observed loaded face: [unrun / observation]
Evidence: [reference]
Expected swap movement: [criterion]
Observed swap movement: [unrun / observation]
Evidence: [reference]
First divergent layer: [source / resource / CSS / mapping / component / render]
Correction owner: [name or team]
Retest trigger: [resource, CSS, browser, content, or component change]
Disposition: [pass / revise / block / unresolved]
Scope limit: [what this record does not prove]Begin with one high-reach text role in one named consumer. Compare its raw fallback, adjusted fallback, loaded primary, and swap path. Keep the adjustment only when the recorded evidence shows that it improves the original failure without creating a worse result in another required state.
Sources
- font-size-adjust CSS property - MDN Web Docs: Documents the
font-size-adjustproperty, selectable font metrics, calculation behavior, and its interaction with overridden@font-facemetrics. - CSS font-size-adjust is now in Baseline: Explains how fonts set to the same CSS size can occupy different amounts of space and describes the property's legibility and layout rationale.
- Creating Perfect Font Fallbacks in CSS: Shows how adjusted local fallback faces use
size-adjust, ascent, descent, and line-gap descriptors, with considerations for weights and italics. - Fallback Font Generator: Demonstrates a local visual comparison between a brand font and its fallback while tuning size, ascent, descent, line gap, and font-size adjustment controls.
- Reducing layout shift with custom fallback fonts: Connects fallback selection and metric overrides with font-loading diagnosis, text reflow, and layout-shift investigation.
- font-display CSS at-rule descriptor - MDN Web Docs: Documents how
font-displaycontrols the display of a font face according to its download readiness and timing. - Best practices for fonts: Treats font loading and delivery as separate concerns, including declarations, hosting, format, subsetting, and cautious preloading.
- Ambient Sage Design Kit: Publishes Ambient Sage as a free kit that uses Plus Jakarta Sans for its primary typography and JetBrains Mono for technical strings.