skip to content

In Flutter 3.47, how do you control a variable font's weight and other axes, and what did Flutter 3.41 change about FontWeight?

level: middleimportance: nice to knowfreq 18%

answer

  1. one file, many faces
  2. four-character axis tags
  3. TextStyle.fontVariations
  4. FontWeight now drives wght
  5. FontVariation.weight overrides it

basics

~10 s

Declare the variable file once in pubspec, then set axes with TextStyle.fontVariations such as FontVariation.width(90). Since Flutter 3.41, fontWeight also sets the wght axis and accepts any value from 1 to 1000.

solid answer

~40 s

A variable font is one file whose outlines vary along design axes identified by four-character tags — `wght`, `wdth`, `slnt`, `ital`, `opsz`. You declare it once in `pubspec.yaml` and set axes through `TextStyle.fontVariations`, a list of `FontVariation(axis, value)` or the named constructors `FontVariation.weight`, `.width`, `.slant`, `.italic` and `.opticalSize`. Since Flutter 3.41, `fontWeight` also sets the `wght` axis, `FontWeight` accepts any value from 1 to 1000 (`FontWeight(550)`), and `FontWeight.index` is deprecated for `value`; an explicit `FontVariation.weight` overrides the `FontWeight`. Apps that relied on `fontWeight` not touching a variable font may render heavier after upgrading — `FontWeight.normal` restores the default. `TextStyle.lerp` interpolates `fontVariations`, fastest when both styles list the same axes in the same order.

code

dart · 23 lines
dart
import 'package:flutter/material.dart';

class ReadAlongWord extends StatelessWidget {
  const ReadAlongWord({super.key, required this.word, required this.emphasis});

  final String word;
  final double emphasis; // 0.0 = resting, 1.0 = being read aloud

  @override
  Widget build(BuildContext context) {
    return Text(
      word,
      style: TextStyle(
        fontFamily: 'ScribblyVariable',
        fontSize: 26,
        fontVariations: <FontVariation>[
          FontVariation.weight(400 + 300 * emphasis),
          const FontVariation.width(100),
        ],
      ),
    );
  }
}

go deeper

for a junior

Know that a variable font is one file with adjustable axes, set through TextStyle.fontVariations.

for a middle

Explain the axis tags and constructors, the 3.41 change to FontWeight, and fontVariations versus fontFeatures.

for a senior

Handle the upgrade regression where text turns heavier, and design axis animations that interpolate cheaply.

for a principal

Judge when one variable file should replace a set of static faces across a product, weighing size, animation needs and design control.

## Static files versus a variable font A **static** font file holds one face: Regular, or Bold, or Light. A **variable font** holds a whole range of faces in one file. Its outlines are defined along **design axes**, and the renderer computes the face for any position on each axis. The children's book app's handwriting face can ship as a single variable file with a weight axis from 300 to 700 instead of three static files — smaller in total, and able to render any weight in between. Axes are identified by four-character **tags** from the OpenType registry: | Tag | Axis | `FontVariation` constructor | Range enforced by the constructor | |---|---|---|---| | `wght` | weight | `FontVariation.weight` | 1 to 1000 | | `wdth` | width | `FontVariation.width` | 0 or more, 100 is normal | | `slnt` | slant | `FontVariation.slant` | greater than -90 and less than 90 degrees | | `ital` | italic | `FontVariation.italic` | 0.0 to 1.0 | | `opsz` | optical size | `FontVariation.opticalSize` | greater than 0 | A font can also define custom axes; the generic `FontVariation('GRAD', 50)` takes any four-character tag. Which axes a file supports, and their ranges, is up to the font; the renderer typically clamps a value to the range the font defines. ## Declaring the variable file In `pubspec.yaml` a variable font is an ordinary family with a single `asset` entry, because one file covers every weight on its axis. Giving it its own family name, such as `ScribblyVariable`, keeps it from mixing with static files of the same design. A family that contains both a static Bold file and the variable file is harder to reason about: face matching picks a file first, and only then does the weight axis apply, so which file wins for a given `fontWeight` is not obvious from the code. Check which axes the file really has before designing around one. A face with only `wght` ignores width and slant settings, and many handwriting faces ship no italic axis at all. ## Declaring and using it The file is declared once, like any other font, and selected by family name. Axis values then go in `TextStyle.fontVariations`: ```dart const TextStyle( fontFamily: 'ScribblyVariable', fontWeight: FontWeight(550), fontVariations: <FontVariation>[FontVariation.width(90)], ) ``` `fontVariations` is for **continuous** parameters. Its sibling `fontFeatures` selects **discrete** OpenType features, such as tabular figures or a slashed zero; the two are easy to confuse in an interview. ## What changed in Flutter 3.41 Before 3.41, `fontWeight` only chose the nearest file within a family. A variable font is a single file, so `fontWeight` picked it and nothing more: the text stayed at the file's default weight unless you also set `FontVariation('wght', …)`. Flutter 3.41 changed that: - `fontWeight` now **also sets the `wght` axis** of fonts that have one; the engine adds the equivalent of `FontVariation('wght', value)` unless the style's `fontVariations` already sets `wght`. - `FontWeight` accepts **any integer from 1 to 1000**, not just `w100` … `w900`. - `FontWeight.index` is **deprecated**; use `FontWeight.value`. - An explicit `FontVariation.weight` in `fontVariations` **overrides** the `FontWeight`. The API docs recommend declaring weight through `FontWeight` and keeping `FontVariation.weight` for special cases. Migration note: an app that set `fontWeight: FontWeight.bold` on a variable font and expected no effect now renders bold text. Set `fontWeight` to the intended value, or `FontWeight.normal` to restore the font's default weight. ## Animating axes Because a variable font renders any position on an axis, it can animate smoothly — for example thickening the word being read aloud in a read-along feature. `TextStyle.lerp` interpolates `fontVariations` axis by axis: 1. It pairs variations by axis tag; an axis missing on one side is interpolated against its absence. 2. It is fastest when both styles list the same axes **in the same order**. 3. `FontWeight.lerp` rounds and clamps to 100-900, so for weights outside that band animate `FontVariation.weight` instead. `FontVariation.opticalSize` has a quieter use in animation: pinning the optical size stops a font that adapts its outlines to text size from visibly shifting while the size animates. ## When to choose a variable font - **Many weights or widths in use** — one variable file often beats several static ones on size. - **Animated weight or width** — only a variable font animates without jumping between files. - **One or two faces only** — static files are simpler and usually smaller.

  • After upgrading past Flutter 3.41, headings in a variable font look heavier. Why?
    Since 3.41 `fontWeight` also sets the variable font's `wght` axis. Before, a `FontWeight.bold` on a single variable file changed nothing, so the text stayed at the file's default weight. Now it really renders bold. Set the intended `fontWeight`, or `FontWeight.normal` to get the default weight back.
  • What is the difference between fontVariations and fontFeatures on a Flutter TextStyle?
    `fontVariations` sets continuous design-axis positions of a variable font, such as weight 550 or width 90, and can be interpolated by `TextStyle.lerp`. `fontFeatures` switches discrete OpenType features on or off, such as tabular figures or a slashed zero, and is not interpolated.

saying these in an interview costs you the question

  • In Flutter 3.47 fontWeight has no effect on a variable font's weight axis.
  • FontWeight only accepts the nine constants w100 through w900.
  • A variable font needs one pubspec asset entry per weight it can render.
  • fontFeatures is how you set a variable font's weight or width.
  • FontWeight.index is the current way to read a weight's numeric value.