skip to content

Custom Typefaces

Custom fonts are declared as families with weights and styles in pubspec, then applied through fontFamily in TextStyle or ThemeData. Interviewers ask about package fonts and bundling vs fetching.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Flutter, how do you bundle a custom font family in pubspec.yaml and apply it app-wide and to a single Text widget?

level: juniorimportance: must knowfreq 55%

answer

  1. a list under the flutter: key
  2. family, fonts, asset
  3. paths relative to pubspec.yaml
  4. one theme constructor argument
  5. a per-widget style override

basics

~20 s

Copy the font files into the project, list them in pubspec.yaml under flutter: fonts: as a family with asset entries, then pass that family name as fontFamily to ThemeData for the whole app or to a TextStyle for one widget.

solid answer

~40 s

Put the `.ttf`/`.otf` files in a folder such as `fonts/`, then declare them in `pubspec.yaml` under `flutter:` → `fonts:` as a `family` with a `fonts` list of `asset` entries, adding `weight` or `style` on the files that are not the regular upright face. The `flutter` tool validates that block, bundles the files and lists them in `FontManifest.json`; they do not also go under `assets:`. Code refers to the font only by its family name: `ThemeData(fontFamily: 'Scribbly')` applies it to the theme's text themes so most Material text picks it up, and `TextStyle(fontFamily: 'Scribbly')` on a `Text` overrides one widget. A family name that matches nothing is not an error — the text silently renders in a fallback font — so check new fonts on a device.

code

yaml · 10 lines
yaml
flutter:
  uses-material-design: true
  fonts:
    - family: Scribbly
      fonts:
        - asset: fonts/Scribbly-Light.ttf
          weight: 300
        - asset: fonts/Scribbly-Regular.ttf
        - asset: fonts/Scribbly-Bold.ttf
          weight: 700

go deeper

for a junior

Recall the exact pubspec shape — flutter:, fonts:, family, fonts, asset — and the two places a family name is used: ThemeData for the app, TextStyle for one widget.

for a middle

Explain what the build does with the block: validation, bundling, FontManifest.json, registration by family name, and why a wrong name falls back silently instead of failing.

for a senior

Show you guard against the silent fallback: a checklist or visual review for new faces, one family per brand voice, and no file names or package prefixes scattered through widget code.

for a principal

Frame the font as a product dependency: licence terms for bundling, which faces ship at all, and whether a single brand family belongs in a shared package every app depends on.

## Typefaces, font files and family names A **typeface** is a design — say the hand-lettered face a children's book app uses for its stories. A **font file** is one concrete member of it: Light, Regular, Bold. Flutter does not install your custom faces into the operating system. It ships the files inside the app's **asset bundle** and registers them with the engine under a **family name** that you choose. Every piece of Dart code then refers to that family name, never to a file path. ## Declaring the family in pubspec.yaml Fonts are declared under the top-level `flutter:` key of `pubspec.yaml`, in a `fonts:` list with one entry per family: ```yaml flutter: fonts: - family: Scribbly fonts: - asset: fonts/Scribbly-Light.ttf weight: 300 - asset: fonts/Scribbly-Regular.ttf - asset: fonts/Scribbly-Bold.ttf weight: 700 ``` - **`family`** — the name Dart code uses. It need not match the file name or the name stored inside the font. - **`fonts`** — the files that belong to the family. - **`asset`** — the path to one file, **relative to `pubspec.yaml`**. - **`weight`** and **`style`** — optional descriptors for the files that are not the regular upright face. `weight` accepts 100 to 900 in steps of 100; `style` accepts `normal` or `italic`. The `flutter` tool validates this block on every build. An unknown key produces an error such as `Unexpected child "…" found under "fonts".`, and a weight such as 650 is rejected. The tool then copies the files into the bundle and writes a generated `FontManifest.json`, which the engine reads at startup to register each family. Font files therefore do **not** need repeating under `assets:`. Flutter supports `.ttf`, `.otf` and `.ttc` files. `.woff` and `.woff2` are not supported on every platform, so keep web-only formats out of a mobile app. ## Applying it to the whole app ```dart MaterialApp( theme: ThemeData(fontFamily: 'Scribbly'), home: const StoryShelf(), ) ``` The `ThemeData` factory applies the family to its `textTheme` and `primaryTextTheme` through `TextTheme.apply`, so Material components that take their text style from the theme — app bar titles, buttons, list tiles, body text — render in it. Two details follow from how that works: 1. `fontFamily` is only a **constructor argument**. `ThemeData` keeps no `fontFamily` field and `copyWith` has no such parameter; to change the family of an existing theme you change its text themes. 2. A widget that passes its own `TextStyle` with a different `fontFamily` keeps it; the theme only fills in what the widget leaves unset. ## Applying it to one widget ```dart const Text('Once upon a time', style: TextStyle(fontFamily: 'Scribbly')) ``` `Text` merges this style over the ambient default text style, so only the fields you set change: size and colour still come from the theme. Use this for one-off elements — a title page, a speech bubble — and the theme for the app's default voice. ## Mistakes interviewers listen for 1. Declaring fonts under `dependencies:` or only under `assets:`. Neither registers a family. 2. Passing a file name such as `Scribbly-Bold.ttf` as `fontFamily`. The family name is the only key. 3. Mis-indenting the YAML so `fonts:` no longer sits under `flutter:`. 4. Assuming a typo fails loudly. The engine finds no family, falls back through `fontFamilyFallback` and then the platform default, and draws the text anyway — the only symptom is the wrong look. 5. Adding a `weight` of 900 to a file that is really bold and expecting heavier text. The key describes a file; it does not transform it. ## Checking that it worked Because a wrong name never fails, verify a new family deliberately. Run the app after `flutter pub get` picks up the edited `pubspec.yaml` and compare a sample screen with the design: handwriting faces are easy to tell apart from the platform default, but a geometric sans can pass for Roboto at a glance. A quick trick is to render one word in each declared weight side by side on a debug screen; a weight that looks identical to its neighbour usually points at a missing or mislabelled file. ## Where a font can come from | Source | Declared where | Selected in code with | |---|---|---| | Files in the app | the app's `flutter: fonts:` | `TextStyle(fontFamily: 'Scribbly')` | | A package that declares the family | the package's own `pubspec.yaml` | `TextStyle(fontFamily: 'Scribbly', package: 'story_fonts')` | | The `google_fonts` package | nothing, or files under `assets:` | `GoogleFonts.lato()` and similar | The first row is the default for a product's brand face: the font is available from the first frame, works offline and costs only its file size.

  • Do the font files also have to be listed under assets: in pubspec.yaml?
    No. Files named in `flutter: fonts:` are bundled by the `flutter` tool and recorded in `FontManifest.json`, which the engine reads to register the family. Listing them under `assets:` as well only makes them loadable as raw bytes. The exception is the `google_fonts` package, which looks for its files under `assets:` by file name instead of through a declared family.
  • What happens when a TextStyle's fontFamily matches no declared family?
    Nothing fails. The engine finds no registered family, tries `fontFamilyFallback` in order, then the platform's default font, and draws the text. Registered family names are looked up with ASCII case folded, but any other mismatch — a missing `package:` argument, a typo — just produces the wrong typeface, so verify new fonts visually on a device.
  • Why does ThemeData.copyWith(fontFamily: 'Scribbly') not compile?
    `fontFamily` exists only as an argument of the `ThemeData` factory, which applies it to `textTheme` and `primaryTextTheme` and then discards it; the class has no `fontFamily` field, so `copyWith` has nothing to copy. To change an existing theme's family, apply it to the text themes, for example `theme.textTheme.apply(fontFamily: 'Scribbly')`, and pass the result to `copyWith`.

saying these in an interview costs you the question

  • Custom fonts are declared under dependencies: in pubspec.yaml.
  • Every font file must also be listed under assets: or it will not load.
  • fontFamily takes the file name, such as Scribbly-Bold.ttf.
  • A misspelled fontFamily throws an exception when the text is laid out.
  • ThemeData.copyWith(fontFamily:) swaps the app font on an existing theme.
open as a page

In Flutter, how does an app use a font family that a dependency package declares, and what does TextStyle's package argument change?

level: middleimportance: should knowfreq 28%

basics

~20 s

A package's declared fonts are registered as packages/<package>/<family>, so the app selects one with TextStyle(fontFamily: 'Scribbly', package: 'story_fonts'), which prefixes the name. Undeclared files under the package's lib/ can instead be declared by the app.

open as a page

In a Flutter pubspec.yaml font family, what do weight and style entries describe, and which file renders when a TextStyle requests an undeclared weight?

level: middleimportance: should knowfreq 40%

basics

~20 s

Weight and style entries describe which face each file is — a weight from 100 to 900 and normal or italic — and cannot change it. A TextStyle gets the closest bundled face; with no close one, Flutter may fake bold or italic.

open as a page

With Flutter's google_fonts package, how are fonts fetched and cached at runtime, and when should a release build bundle the font files instead?

level: seniorimportance: should knowfreq 38%

basics

~20 s

google_fonts looks for each face in the app's assets, then a device cache, then downloads it over HTTP and caches it. Ship the files as assets and set allowRuntimeFetching to false when text must render offline and without a font swap.

open as a page

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%

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.

open as a page