Skip to content

Typography: Let variable fonts use any weight in their range - #83141

Open
Jiwoon-Kim wants to merge 2 commits into
WordPress:trunkfrom
Jiwoon-Kim:draft/font-variations
Open

Jiwoon-Kim wants to merge 2 commits into
WordPress:trunkfrom
Jiwoon-Kim:draft/font-variations

Conversation

@Jiwoon-Kim

@Jiwoon-Kim Jiwoon-Kim commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

What?

When the selected font family is variable, the Appearance control splits into a Style select, with the choices it already offers, and a Weight control. As with the font size, the weight is picked from a list, here the hundreds inside the font's range shown with their numbers (Light (300)), and a toggle next to the Weight label switches to a slider and a number field for any value in the range. Static fonts keep the combined style and weight list.

Both controls write the same fontStyle and fontWeight values as Appearance, so nothing changes in what is saved or printed: a weight of 178 is fontWeight: "178" and font-weight: 178.

Why?

Appearance offers fixed combinations such as "Bold Italic". That matches static fonts, which ship one file per style and weight. A variable font draws any weight in its range ("fontWeight": "100 900" in theme.json), but the control lists only the hundreds, so 178 or 425 can't be chosen, and a saved value that is not a hundred shows as "Default".

How?

  • getFontWeightRange() reads the weight range that a family's faces declare. A family with a range is treated as variable; no new flag is needed.
  • Both ends of a range are read by parseFontWeightValue(), a module the appearance list and this control share, so a face declaring "normal 900" is the range 400 to 900 in both. Before, the two had a parser each: Typography: List only the weights inside a variable font's range #83128 taught the appearance list the keywords @font-face accepts, and the weight range kept its parseInt, which read normal as nothing and left the control with no range to offer. lighter and bolder are relative to a parent, so @font-face does not take them and a range naming one is still left alone rather than guessed at.
  • VariableFontAppearanceControl renders the Style select and a Weight control. The Weight control follows the font size picker: a select with the hundreds inside the range, and a toggle beside the label for a slider and a number field. The number field is separate from the slider because RangeControl's own input clamps the value it shows.
  • A saved weight that is not a preset opens in the direct input and shows as Custom (178) in the select. A saved weight outside the font's range is kept and shown as it is, with a short note; it only changes when the user sets another. The slider only moves within the range.
  • Style keeps the options and the behaviour of Appearance, including italic for fonts without an italic face.
  • When the font family changes, a weight inside the new variable font's range is kept. Before, a weight that was not one of the listed hundreds was replaced by the nearest one.
  • The weight stays in fontWeight rather than in font-variation-settings. font-variation-settings is inherited and applied after font-weight, so with 'wght' 300 on a paragraph, a <strong> inside it also renders at 300. It also only reaches fonts that have a wght axis: a fallback without one, such as a system font drawing a script the web font does not cover, stays at the element's font-weight, and faux bold is decided from font-weight as well.

Not in this PR: other axes. Slant (fontStyle: oblique <angle>) and width (fontStretch) map to CSS properties and could join Appearance later; axes without one, such as GRAD, would be stored in a fontVariationSettings style property and shown in a separate Font variations panel, which a theme can turn on. That model is proposed in #83148 and prototyped in #83159. Also open: whether italic should be offered when a font has no italic face.

Testing Instructions

  1. Add a variable font family with a face whose fontWeight is "250 750", plus a static family with "400" and "700" faces, to the active theme's theme.json.
  2. In the editor, select a paragraph and set its font to the variable family. Typography > Appearance shows a Style select and a Weight select listing Light (300) to Bold (700).
  3. Click the settings icon next to the Weight label. A slider and a number field appear; enter 178 or drag. The block gets font-weight: 178, in the editor and on the front end.
  4. Click the icon again: the select shows Custom (178).
  5. Save a paragraph with fontStyle: normal and fontWeight: 200 in that family, for example from the code editor, then select it. The Weight control opens with 200 in the number field, the slider at 250, and a note that 200 is outside the range (250–750). The value stays 200 until another weight is chosen.
  6. Set a paragraph to a variable family with a range of 100–1000 and weight 420, then change its font to the 250–750 family: the weight stays 420.
  7. Set the font to the static family: the Appearance control is shown as before.
  8. Add a third family with a face whose fontWeight is "normal 900", and set a paragraph to it: the Weight select lists Regular (400) to Black (900), and the slider runs from 400 to 900.

Testing Instructions for Keyboard

  1. Tab to the Style select; it opens with Enter and is navigated with the arrow keys.
  2. Tab to the Weight select; it opens with Enter and is navigated with the arrow keys. Tab to the settings icon next to the label and press Enter to switch to direct input.
  3. In direct input, the slider moves by 1 with the arrow keys and Home goes to the lowest weight; Tab reaches the number field, where the arrow keys also change the weight by 1 and any value can be typed.

Use of AI Tools

AI tools (Claude Code, Claude Opus 5, and Codex) assisted with implementation and testing.

@github-actions

Copy link
Copy Markdown

🤖 PR meta 🤖

🏷️ Labels

This pull request needs exactly one label indicating its type, and has 0.

  • Required: any label starting with [Type].
  • Found: none.

Read more about Type labels in Gutenberg. If you cannot add labels yourself, a reviewer can do it for you.

Jiwoon-Kim and others added 2 commits September 26, 2026 02:19
`getFontWeightRange()` read each end of a face's `fontWeight` with `parseInt`,
so a face declaring `"normal 900"` was skipped and the weight control offered
nothing, while the appearance list beside it read that same face correctly
through its own parser. WordPress#83128 taught one of the two about the keywords the
property accepts; this teaches both from one place.

`parseFontWeightValue()` moves to its own module and both callers import it.
Nothing about how either reads a range changes otherwise: the keywords are
still `normal` and `bold`, `lighter` and `bolder` are still refused as relative
to a parent and not allowed on `@font-face`, and a range naming one is still
left alone rather than guessed at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Jiwoon-Kim
Jiwoon-Kim force-pushed the draft/font-variations branch from 1348882 to fa3a8dc Compare September 25, 2026 17:21
@Jiwoon-Kim
Jiwoon-Kim marked this pull request as ready for review September 25, 2026 17:42
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Jiwoon-Kim <kimjiwoon@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@Jiwoon-Kim

Copy link
Copy Markdown
Contributor Author

@t-hamano @juanfra, this is the follow-up to #83128, now that the range parsing is in trunk: when a family is variable, Appearance splits into a Style select and a Weight control that reaches any weight in the range, not only the hundreds. I have rebased the branch, so it is the feature commit plus one more — both controls now read a face's weight range through the same module, which is what taught the weight range the keywords #83128 taught the appearance list. A face declaring "normal 900" used to be read by one and skipped by the other.

Out of scope here, and not a PR yet: the Style select beside it. Checking this branch against Roboto Flex, whose faces declare normal and oblique 0deg 10deg, the select offers Regular, the two-angle range as written, and a faux Italic for a font that has slnt and no ital axis. The raw range is #83456, which resolves it to a style the property accepts. Giving slnt an angle of its own, and deciding what Italic should mean for such a font, is the Style side of #83148; I have not opened anything for it.

Would you have a moment to look? It also needs a [Type] label, which I cannot add myself — [Type] Enhancement if that reads right to you.

Jiwoon-Kim added a commit to Jiwoon-Kim/gutenberg that referenced this pull request Sep 27, 2026
…raft

The Font variations panel and the capability controls beside it are one
model seen from two sides: the panel edits the axes no CSS property owns,
while Style, Weight and Width edit the ones that do, and both read what a
family's faces declare. Reviewing them apart means reading the same faces
twice and guessing how the halves meet.

This merge brings the prototype in so the whole typography surface can be
read in one diff and tried in one editor. It carries the weight work of
WordPress#83141 with it, deliberately: the panel cannot be judged with one of its
three controls missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Block editor /packages/block-editor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant