Using variable fonts in theme.json

Post

If you are trying to use a variable font in your custom block theme via theme.json, you may be running into some issues that I also saw and, mostly, solved.

As of Gutenberg 16.1.2 and WordPress 6.2, there are no editor controls for variable font axes—the thing that allows you to control things like weight on a sliding scale instead of as discreet values (400 for normal, 700 for bold, etc.). However, if you’re developing a custom theme, do you really want to have to adjust sliders for every heading and bit of text manually? Instead, I’ve taken a different approach defining separate font families for each style I want to use.

Before we dive into variable font settings, let’s take a brief look at how fonts are defined in theme.json. If you’ve already got a handle on that, skip down to the solution.

Defining fontFamilies

Adding fonts that are selectable in the editor is as simple as adding a fontFamilies array to theme.json:

JSON
{
	"settings": {
		"typography": {
			"fontFamilies": [
				{
					"fontFamily": "'Josefin Sans', sans-serif",
					"name": "Title",
					"slug": "title"
				}
			]
		}
	}
}

Each object in the array defines a selectable font in the editor. The name is the label that is shown in the editor, and the slug must be a unique string that gets turned into a CSS custom property (or variable) like --wp--preset--font-family--title. The fontFamily is the equivalent of the font-family CSS property, which means that you should not only add the intended font family name, but also as many fallbacks as you want. Here I’ve just added sans-serif as the fallback, which will use the default sans-serif font defined by the user’s system. Ideally, you would painstakingly select a curated list of similar-looking fonts that may or may not be available to a user, but perhaps this practice is a holdover from the old days.

Note that this will let you select a font family in the editor called “Title”, but will not necessarily display the font unless you’ve enqueued the font assets yourself elsewhere. I won’t bore you with how we used to load custom font assets, since there’s a better way now.

Using fontFace

Thankfully, since the Web Fonts API was introduced in Gutenberg 12.8 and WordPress 6.0, you don’t need to manually load font assets anymore. The fontFace property of a fontFamilies object lets you define just about everything you would if you were writing an @fontFace CSS declaration, including the path to the font file assets:

JSON
{
	"settings": {
		"typography": {
			"fontFamilies": [
				{
					"fontFamily": "'Josefin Sans', sans-serif",
					"name": "Title",
					"slug": "title",
					"fontFace": [
						{
							"fontFamily": "Josefin Sans",
							"fontStyle": "auto",
							"fontWeight": "100 900",
							"fontDisplay": "swap",
							"src": [
								"file:./assets/fonts/josefinsans-subset.woff",
								"file:./assets/fonts/josefinsans-subset.woff2"
							]
						}
					]
				}
			]
		}
	}
}

The most important (and required) properties that must be defined are fontFamily and src. This fontFamily property is slightly different than the one in the parent object, as this is the singular name that the font will be accessed by, which should correspond to the first item in the parent fontFamily list.

You can reference MDN for values accepted by fontStyle and fontWeight. Generally, if it’s a static (non-variable) font, you’re just letting the browser know what weight and style the font provides. This example happens to use a variable font (slid that right in,did you notice?), so I’m setting a range of 100 900 which is the range the variable font provides.

And finally, the magic: the src array points to the font files in your theme that you have downloaded from Google Fonts or otherwise procured. I’m using Glyphhanger to subset and format fonts into woff and woff2 files (you probably only need the latter).

Notice that fontFace is an array—for a static font, you’ll generally have a few different files, one for each weight & style combo, which you’ll need to add as different objects in the array. If you do, be sure to use the same fontFamily name so that you’re just adding available styles and weights to one selectable typeface. The need to load a bunch of different files is reduced or sometimes completely unnecessary with variable fonts.

Use the same variable font with different variation settings as separate font families

In order to make use of a variable font like Recursive, which has more than just a weight and slant axis, my solution is to use the same variable font file while defining separately-named font faces. This gives us font options in the editor to use a cursive style and monospace style, which are custom axes that Recursive provides.

This is accomplished with the fontVariationSettings property, which is how we can set all of the different axis values on a variable font:

JSON
{
	"settings": {
		"typography": {
			"fontFamilies": [
				{
					"fontFace": [
						{
							"fontDisplay": "swap",
							"fontFamily": "Recursive",
							"fontStyle": "auto",
							"fontVariationSettings": "'MONO' 0, 'CRSV' 0, 'CASL' 0, 'slnt' 0",
							"fontWeight": "300 900",
							"src": [
								"file:./assets/fonts/recursive-subset.woff",
								"file:./assets/fonts/recursive-subset.woff2"
							]
						},
						{
							"fontDisplay": "swap",
							"fontFamily": "Recursive",
							"fontStyle": "italic",
							"fontVariationSettings": "'MONO' 0, 'CRSV' 0, 'CASL' 0, 'slnt' -14",
							"fontWeight": "300 900",
							"src": [
								"file:./assets/fonts/recursive-subset.woff",
								"file:./assets/fonts/recursive-subset.woff2"
							]
						}
					],
					"fontFamily": "Recursive, sans-serif",
					"name": "Sans-serif",
					"slug": "sans-serif"
				},
				{
					"fontFace": [
						{
							"fontDisplay": "swap",
							"fontFamily": "Recursive Cursive",
							"fontStyle": "auto",
							"fontVariationSettings": "'MONO' 0, 'CRSV' 1, 'CASL' 1, 'slnt' 0",
							"fontWeight": "300 900",
							"src": [
								"file:./assets/fonts/recursive-subset.woff",
								"file:./assets/fonts/recursive-subset.woff2"
							]
						},
						{
							"fontDisplay": "swap",
							"fontFamily": "Recursive Cursive",
							"fontStyle": "italic",
							"fontVariationSettings": "'MONO' 0, 'CRSV' 1, 'CASL' 1, 'slnt' -14",
							"fontWeight": "300 900",
							"src": [
								"file:./assets/fonts/recursive-subset.woff",
								"file:./assets/fonts/recursive-subset.woff2"
							]
						}
					],
					"fontFamily": "'Recursive Cursive', cursive",
					"name": "Cursive",
					"slug": "cursive"
				},
				{
					"fontFace": [
						{
							"fontDisplay": "swap",
							"fontFamily": "Recursive Monospace",
							"fontStyle": "auto",
							"fontVariationSettings": "'MONO' 1, 'CRSV' 0, 'CASL' 0, 'slnt' 0",
							"fontWeight": "300 900",
							"src": [
								"file:./assets/fonts/recursive-subset.woff",
								"file:./assets/fonts/recursive-subset.woff2"
							]
						},
						{
							"fontDisplay": "swap",
							"fontFamily": "Recursive Monospace",
							"fontStyle": "italic",
							"fontVariationSettings": "'MONO' 1, 'CRSV' 0, 'CASL' 0, 'slnt' -14",
							"fontWeight": "300 900",
							"src": [
								"file:./assets/fonts/recursive-subset.woff",
								"file:./assets/fonts/recursive-subset.woff2"
							]
						}
					],
					"fontFamily": "'Recursive Monospace', monospace",
					"name": "Monospace",
					"slug": "monospace"
				}
			]
		}
	}
}
Expand

Expand the code block above to see more, but the gist is that for each style of font I want to be selectable in the editor, I’ve defined a font face with a unique name for each that uses the same font files. For this font in particular, I had to define separate font faces for the normal (auto) and italic styles, which just sets a different value for the slnt axis in fontVariationSettings.

Now I can choose any of these styles as different fonts in the typography area in the editor (you may need to open the kebab menu to get the font family options):

Screenshot of the Typography section of the editor block sidebar showing the available selection options for font: Default, Title, Sans-serif, Cursive, and Monospace.
Screenshot of example text in sans-serif, cursive, and monospace styles.

At the time of posting, this is exactly what I’m using in this theme. If you wanted, you could use a single variable font with different styles defined like this to use for headings, body text, etc. And you can set the default font family for all headings, for instance, like this:

JSON
{
	"styles": {
		"elements": {
			"heading": {
				"typography": {
					"fontFamily": "var(--wp--preset--font-family--cursive)",
					"fontStyle": "normal",
					"fontWeight": "600"
				}
			}
		}
	}
}

I’m still using CSS to handle some things, like links. While you can set the font family of links similar to headings via theme.json, I wanted to use the variable font for something that static fonts can’t do: animate.

SCSS
a:where(:not(.wp-element-button)) {
	font-variation-settings: "MONO" 0, "CRSV" 1, "CASL" 1, "slnt" 0;
	transition: font-variation-settings var(--wp--custom--transitions--default)
		ease-in-out;

	@media prefers-reduced-motion {
		transition: none;
	}

	&:hover,
	&:focus {
		font-variation-settings: "MONO" 0, "CRSV" 1, "CASL" 1, "slnt" -14;
	}
}

Variable fonts in the Editor

Original post 2023-07-20:

Unfortunately, as of Gutenberg 16.1.2 and WordPress 6.2, my fonts don’t quite display properly in the editor. Maybe it’s just this specific font, but the sans-serif style is for some reason acting as if it’s both monospace and not, and also casual when it shouldn’t be.

Here’s a screenshot of what the previous paragraph and heading looks like in the editor:

Screenshot of the previous paragraph and heading in the editor. The fonts appear to be using the "casual" style when they shouldn't be, and the letter spacing is normal but the letters that would usually shrink for monospace (m, w) are shrunk, leaving weird gaps in the text.

It’s frankly a little hard to look at, but I’ll update this post if I figure out a workaround or if some new version of Gutenberg somehow fixes the situation.

Are you using variable fonts in custom block themes? Have you run into the same issues or found other solutions? Let me know!

3 responses

  1. Tom Avatar

    YOU’RE AMAZING! I’ve been seriously sweating how to make this happen, both for myself and for a client site. Thanks, Cory!

  2. […] (July 20, 2023) • Cory Hughart | Using variable fonts in theme.json […]

  3. […] Using Variable Fonts in theme.json – Tips for using variable fonts in WordPress block themes. […]

Broadcast a message

This site uses Akismet to reduce spam. Learn how your comment data is processed.