Testing Cairo's oblique italic across browsers
Cairo has no italic masters — its italic is entirely the slnt variable axis, not a
separate face. Asking a browser to slant it (font-style: italic, oblique, an explicit axis value, or some combination) can resolve very differently
depending on how you ask and which engine is rendering it. This page tests every viable technique,
in real engines and on real devices, to work out which ones are safe to rely on and which ones are
traps — and to justify the pattern /use tells consumers to write.
Every technique below is already asserted in Chromium, WebKit and Firefox by src/lib/fonts/slant.browser.test.ts, which measures rendered shear on every pnpm test; results are in docs/compat.md. This page exists for the one
thing that cannot automate: real Safari, which Playwright's WebKit is not.
TL;DR
Summary: ship font-style: oblique 11deg + explicit font-variation-settings: 'slnt' -11 (the recommended row below) — this is what fonts.css now ships, confirmed correct
on every tested case: Chromium, Firefox, Playwright's WebKit, and real Safari 18.7 AND 27.0, including against the normal-style sibling face fonts.css always
ships alongside this one. It fixes two SEPARATE problems at once — see below.
- Two separate problems, easy to conflate: (1) a Chromium/WebKit synthesis bug, triggered
when a use-site keyword's angle falls outside a declared
@font-facerange; (2) real Safari 18.7 performing no automaticfont-style→slntmapping at all, for any keyword, exact angle or not — a device/version gap, not an engine bug. Stating the exact angle fixes problem 1. Pairing it with an explicitslntfixes problem 2. You need both. font-style: oblique <angle>is the CSS Fonts 4 spec's own mechanism for matching a specific point inside a declaredslntrange — not a workaround. Bareoblique(no angle) falls back to a stated default of 14deg;italic's angle is spec-defined as unspecified. Neither is guaranteed to fall inside this face's declared -11deg..11deg bounds, and neither does — that's problem 1 above. State the exact angle instead.- The exact angle alone is still not enough — confirmed BROKEN on real Safari 18.7
(that's problem 2). Pair it with
font-variation-settings: 'slnt' -11: an element's own explicit value directly sets the axis, regardless of whether font-style-matching succeeds on a given engine or version. Confirmed correct on both real Safari 18.7 and 27.0. - This required checking the tested versions, not "every version" — only two real Safari data points exist (18.7 and 27.0); the exact version the gap closed in between them is unknown.
- A family carrying both a normal and a bare oblique face together is a separate,
historical hazard: Chromium and WebKit pick the wrong face for an angled request and render
upright. This is no longer what
fonts.cssships — the recommended row's ranged face does not have this ambiguity — but it's a real trap if you ever hand-roll a face this way yourself. See "historical hazard" below. - Change weight with the
font-weightproperty (font-weight: 700), not by stating'wght'throughfont-variation-settings. Measured: a lone'wght'value alone doesn't breakslnteither, in a flat, unpinned case — but the property can never interact withslntat all, in any component tree, so it carries none of the font-variation-settings-replaces-not-merges risk (the "second real-device finding" below) by construction.
Independently corroborated: Safari has lagged on this exact CSS Fonts 4 behaviour since at least
2020 — see ArrowType's vf-slnt-test, a community test suite (last run November 2022) that found Safari passing almost nothing
except font-variation-settings itself against variable fonts, across three years of
retests. Its recommendation to fall back to transform: skew() is for fonts missing the requested axis value entirely — a different, spec-acknowledged ambiguous case
— not this one: Cairo has a real slnt axis and font-style: oblique matches it directly.
Quick matrix
Rows are what the @font-face declares; columns are what's set at the use site. Each
populated cell overlays two renderings of the same glyphs: pink is
the always-correct control (font-variation-settings: 'slnt' -11 directly, on the same
family), the other colour is the technique being tested. Aligned, the
two colours screen together into a pale blend — visible pink is a failure, not a subtle
one. Blank cells are untested or not a meaningful combination for that face, not "assumed fine."
Full detail — why each populated cell is what it is — is in "Slant techniques" below.
@font-face { font-style: oblique -11deg 11deg; }The font's real slnt bounds — shipped by fonts.css | @font-face { font-style: oblique; }The font's style only — historical, no longer shipped | |
|---|---|---|
0. (nothing set) | VIZChitra VIZChitra trivial — nothing here requests a slant, not a meaningful test | VIZChitra VIZChitra trivial — nothing here requests a slant, not a meaningful test |
1. font-style: italic; | VIZChitra VIZChitra | VIZChitra VIZChitra |
2. font-style: oblique; | VIZChitra VIZChitra | VIZChitra VIZChitra |
3. font-style: oblique 11deg; | VIZChitra VIZChitra | VIZChitra VIZChitra |
4. font-variation-settings: 'slnt' -11; | VIZChitra VIZChitra | VIZChitra VIZChitra |
5. font-style: oblique 11deg; | VIZChitra VIZChitra | VIZChitra VIZChitra |
- pass
- fail
- untested on a real device
- Hover a badge for the exact device/version.
Firefox 155.0
Chromium 153.0.8010.12
WebKit 26.6 (Playwright)
Safari 27.0 (macOS desktop)
Safari 18.7 (iPhone XR)
The iOS Safari backslant
The original bug, and the reason this page exists. Reported on Safari 18.7, iPhone XR:
Cairo renders leaning backwards when slnt is left to the font's default. Desktop
engines look fine, so this only shows up on an actual old device.
The cause is that font-variation-settings replaces the inherited value rather
than merging with it — so declaring only 'wght' silently discards any upright reset.
The @font-face descriptor cannot save you here: it is measurably inert in WebKit.
Left: axis unpinned — Right: 'slnt' 0 stated
font-variation-settings: 'wght' 600 vs 'wght' 600, 'slnt' 0 These must look identical. If the left one leans back, this device has the bug. The fix is
component-level, not a global reset: any rule that states 'wght' via font-variation-settings must also state 'slnt' (src/lib/fonts/axis-pinning.test.ts enforces this for this repo's own code). An
earlier version of this fix instead pinned 'slnt' 0 on html in app.css — that broke font-style: italic everywhere, on every engine,
because an ancestor's explicit slnt blocks the browser's automatic mapping from font-style onto the axis. The "Hazard demo" section further down re-checks that on
this device.
Slant techniques — judge by eye
Each row shows the technique on the left and an untouched upright control on the right. A failure looks like the two halves matching.
✓ Recommended font-style: oblique 11deg + font-variation-settings: 'slnt' -11 — RECOMMENDED, what fonts.css ships
font-style: oblique 11deg; font-variation-settings: 'slnt' -11 (face declares: font-style: oblique -11deg 11deg) Two SEPARATE problems, one pairing that fixes both. Problem one: bare font-style: oblique (no angle) implies a default that falls outside a declared range, tripping a Chromium/WebKit synthesis bug (see the trap rows below) — fixed by stating the EXACT angle, 11deg, instead. Problem two: real Safari 18.7 performs NO automatic font-style -> slnt mapping at all, for any font-style value, exact angle or not — a completely different, device-level gap that stating the angle correctly does nothing for. This pairing fixes both at once: the exact angle sidesteps problem one, and the explicit slnt value sidesteps problem two by not depending on the mapping succeeding at all — an element's own font-variation-settings always wins over whatever font-style-matching would or would not do. Confirmed correct in Chromium, Firefox, Playwright WebKit, and on real Safari 18.7 AND 27.0 — including against a family carrying the normal-style face fonts.css always ships alongside this one, not just an isolated face with no sibling (slant.browser.test.ts). The slnt half is provably safe to delete later, once the mapping is trusted on every version in the field — not yet.
⚠ Incomplete font-style: oblique 11deg ALONE against the TRUE range — correct, but not sufficient by itself
font-style: oblique 11deg (face declares: font-style: oblique -11deg 11deg) This is what you get if you drop the slnt half of the recommended row above — do not. Unlike the bare keyword, 11deg IS within this face's declared range, so there is nothing for Chromium or WebKit to fall back to synthesis for — resolves via the axis alone, correctly, in every automated engine, and confirmed on real Safari 27.0. But confirmed BROKEN on real Safari 18.7: that device performs no automatic font-style -> slnt mapping at all, exact angle or not, the same gap every other font-style-only row in this matrix hits. Getting the angle right solves the synthesis trap; it does not solve the device-support gap — those are two different problems (see the recommended row above).
✕ Avoid font-style: italic against the TRUE -11deg..11deg range — broken, differently
font-style: italic; font-synthesis: weight style (face declares: font-style: oblique -11deg 11deg) Per spec, italic's angle is not 14deg like bare oblique's — it is explicitly "unspecified" (CSS Fonts 4) — so this is a DIFFERENT gap from the row below, not the same one restated: whatever angle an engine picks for italic, it is not guaranteed to fall inside this face's declared -11..11 bounds either, and it doesn't here. Broken in the same two engines as the row below, but by a DIFFERENT amount: Chromium leans ~0.44, the same double-stack shape as bare oblique (axis correctly set to -11, plus a synthetic skew on top). WebKit leans ~0.25 this time, not ~0.44 — that's tan(14deg), a PURE synthetic skew with NO axis contribution at all, a third, genuinely different failure mode. Measured, not assumed to match the row below just because both are "the trap." Only Firefox is correct. Confirmed broken on real Safari, both 18.7 and 27.0. font-synthesis is forced back to weight+style here — this site's own font-synthesis: weight would otherwise neutralise the trap by accident.
✕ Avoid font-style: oblique (BARE) against the TRUE range — still the trap, avoid
font-style: oblique; font-synthesis: weight style (face declares: font-style: oblique -11deg 11deg) -11deg..11deg is not an arbitrary or mismatched range — it's Cairo's actual fvar slnt bounds (min -11, default 0, max 11), and it is what fonts.css now ships. The trap is specifically the BARE keyword: CSS Fonts 4 says lack of an angle implies 14deg, which is OUTSIDE this face's declared bounds, so Chromium and WebKit fall back to synthesis to reach 14deg on top of the (correctly matched) axis — Chromium leans roughly twice as far, WebKit prefers synthesis over the axis outright and lands near the same overshoot. Only Firefox is correct. This is exactly why the recommended pattern above is not optional: writing bare font-style: oblique against the face fonts.css actually ships, without the exact angle, hits this trap. font-synthesis is forced back to weight+style here — this site's own font-synthesis: weight would otherwise neutralise the trap by accident. Confirmed broken on real Safari, both 18.7 and 27.0.
✕ Avoid font-style: oblique 11deg against a family with BOTH normal and BARE oblique — historical hazard
font-style: oblique 11deg (family declares font-style: normal AND a BARE font-style: oblique, no range — no longer what fonts.css ships) CSS Fonts 4: OpenType's slnt axis is positive counter-clockwise, CSS's oblique angle is positive clockwise — so stating the angle here SHOULD target 'slnt' -11 via font-style-matching alone. It does, in Firefox. It does NOT in Chromium or WebKit: both pick the wrong face and render upright — not a partial lean, no lean at all. This is specifically about a BARE oblique descriptor competing ambiguously with a normal sibling for an angled request — fonts.css no longer ships a face shaped like this (see the recommended row above), but the hazard stays real if you ever hand-roll a face this way yourself: a bare oblique descriptor gives the matching algorithm no bounds to prefer it unambiguously. A RANGED oblique descriptor does not have this problem (see 'oblique 11deg ALONE' above) — confirmed broken on real Safari, both 18.7 and 27.0.
○ Legacy font-style: oblique + font-variation-settings: 'slnt' -11 — historical, still valid
font-style: oblique; font-variation-settings: 'slnt' -11 (face declares: font-style: oblique, no range) What fonts.css recommended before the migration to a declared range (see the RECOMMENDED row above) — still correct today, against a face with no declared range, and still a valid choice if you ever ship or consume a face shaped this way. font-style: oblique is the semantically correct way to ask for this (CSS Fonts 4 font-style-matching); the explicit slnt value is what makes it deterministic regardless of whether a given engine performs that mapping at all — an element's own explicit font-variation-settings always wins over any automatic mapping, so whichever half an engine honours, the other covers it. Confirmed by hand on both real Safari 18.7 and 27.0.
⚙ Ingredient font-variation-settings: 'slnt' -11 alone — the deterministic ingredient
font-variation-settings: 'slnt' -11 Leans forward. This sets the axis directly — no automatic font-style mapping involved, so nothing to disagree about across engines, versions, or which face is in play, and immune to the ancestor-pin hazard below. It is one half of the recommended pattern above, not a replacement for font-style: pairing it with font-style keeps the semantic layer without giving up determinism. Confirmed by hand on both real Safari 18.7 and 27.0, not just reasoned from the mechanism.
○ Legacy font-style: oblique alone (bare, no italic), against a range-free face — historical
font-style: oblique (face declares: font-style: oblique, no range) Per the CSS Fonts 4 font-style-matching algorithm, this is an exact match against the face's own font-style: oblique descriptor — no italic-to-oblique fallback step. Leans ~11deg in Chromium, current WebKit and Firefox. On real devices this is confirmed version-dependent, not a permanent Safari limitation: broken on Safari 18.7 (iPhone XR), fixed by Safari 27.0 (macOS desktop) — the exact version this changed in is unknown, only these two endpoints are confirmed. Pair with explicit slnt for determinism on anything between those two — this is exactly the same lesson as the RECOMMENDED row at the top, just against a range-free face rather than the one fonts.css now actually ships.
○ Legacy font-style: italic against a range-free bare `oblique` face — historical
font-style: italic (face declares: font-style: oblique, no range) Leans ~11deg in Chromium, current WebKit and Firefox — but confirmed UPRIGHT on real Safari 18.7 (iPhone XR): it does not perform this automatic font-style -> slnt mapping at all, even with no ancestor pin in the way. Confirmed fixed on Safari 27.0, matching the bare-oblique row above — this time measured, not the earlier guess that it was "probably fixed alongside it." Playwright’s WebKit is a different, newer build and reproduces neither gap — this is a real engine divergence you can only catch by hand. font-style: italic is fine as a semantic hint; pair it with explicit slnt (or prefer bare oblique, one layer less indirect) for determinism.
ℹ Diagnostic HISTORICAL, DO NOT USE — font-variation-settings as an @font-face DESCRIPTOR
@font-face { font-variation-settings: 'slnt' -11 } Not a live recommendation — kept as a regression guard. The old hand-written font.css relied on this. Measured DEAD in WebKit — upright, no lean at all — so Cairo italic was silently broken in Safari all along, the whole time that file was live. Demoted out of the Quick matrix above: it's not a viable alternative worth comparing side by side with the working techniques, just a documented reason not to reintroduce it. Not separately device-confirmed in isolation from the original backslant bug report; not re-tested here.
ℹ Diagnostic Faux-oblique synthesis on top of a real axis
font-style: italic + font-variation-settings: 'slnt' -11 Should look the SAME as the recommended row above. If it leans noticeably further, the engine is synthesising a skew on top of the real axis — fix with font-synthesis: none.
Self-checking
These change measurable metrics, so the page tests them itself.
- Measuring…
Hazard demo: an ancestor pin kills italic (deliberately broken — not our CSS)
The middle half is expected to look upright, unlike the left — this is the exact bug an earlier
version of the iOS fix reintroduced by pinning slnt on html. If app.css ever adds such a pin back, this middle half is how it would show up. Both use
a bare specimen with no font-variation-settings of their own — not .sample .m-oblique, which now states font-variation-settings: normal on
itself and so is deliberately immune to any ancestor, this one included.
On real Safari 18.7 (iPhone XR), the left half shows upright too — not because of the
ancestor pin, but because that engine does not perform the automatic font-style → slnt mapping at all, pin or no pin (fixed by Safari 27.0 —
see the TL;DR above). The left and middle halves become visually indistinguishable there, which is
itself the case for pairing font-style with an explicit value rather than relying on
the mapping alone: you cannot tell "blocked by an ancestor" apart from "this version never
supported it" by eye. The right half sidesteps the whole question — it sets font-variation-settings: 'slnt' -11 directly, so there is no automatic mapping to be
blocked or unsupported, and it leans correctly under the very same pinned ancestor, on every
engine and version including old real Safari. This is the deterministic half of the recommended
pattern above, not a reason to drop font-style entirely.
Left: italic, no ancestor pin. Middle: same, under a pinned ancestor — stays upright. Right: explicit slnt, under the SAME pinned ancestor — leans correctly regardless
font-style: italic vs (ancestor: font-variation-settings: 'slnt' 0) font-style:
italic vs (same ancestor) font-variation-settings: 'slnt' -11 correct only where italic is supported
broken on purpose
explicit slnt stays immune