In Flutter, how does an app use a font family that a dependency package declares, and what does TextStyle's package argument change?
answer
- families get a namespace
- packages/<name>/<family>
- the package argument prefixes
- the package itself passes it too
- lib/ is implied in asset paths
basics
~20 sA 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.
solid answer
~40 sWhen a dependency declares a family in its own `pubspec.yaml`, the `flutter` tool bundles it into the app under the namespaced family `packages/story_fonts/Scribbly`. A plain `TextStyle(fontFamily: 'Scribbly')` therefore misses it and silently falls back; you write `TextStyle(fontFamily: 'Scribbly', package: 'story_fonts')`, and the constructor stores `packages/story_fonts/Scribbly` in `fontFamily` and prefixes every `fontFamilyFallback` entry the same way. The package's own widgets must pass `package:` too. `ThemeData` accepts the same `package:` alongside `fontFamily`. The alternative is a package that ships font files under `lib/` without declaring them: the app declares its own family with `asset: packages/story_fonts/fonts/Scribbly-Regular.ttf` (the `lib/` is implied), bundles only the faces it wants, and selects the family without a `package:` argument.
code
dart · 17 linesimport 'package:flutter/material.dart';
class StoryTitle extends StatelessWidget {
const StoryTitle(this.title, {super.key});
final String title;
@override
Widget build(BuildContext context) {
return Text(
title,
// story_fonts declares Scribbly in its own pubspec.yaml,
// so the family is registered as packages/story_fonts/Scribbly.
style: const TextStyle(fontFamily: 'Scribbly', package: 'story_fonts', fontSize: 32),
);
}
}go deeper
Remember that fonts from a package are namespaced and need the package argument on TextStyle.
Explain both routes — package-declared versus app-declared from package files — and what the package argument does to fontFamily and fontFamilyFallback.
Choose the route for a shared design system, and diagnose the silent fallback caused by a missing or extra package argument.
Decide whether brand typography lives in a shared package every app depends on, and how its versioning and licensing are governed across apps.
## Why share a font through a package A publisher building several children's book apps wants every app to use the same hand-lettered family. Copying the files into each app drifts: one app gets the new Bold, another keeps the old one. Putting the files in a package — call it `story_fonts` — gives every app one versioned source. Flutter supports two ways to do it, and they differ in who declares the family. ## Option 1: the package declares the family The package's own `pubspec.yaml` has a normal `flutter: fonts:` block. When an app depends on the package, the `flutter` tool includes those families in the app's bundle but **namespaces** them: the family is registered as `packages/story_fonts/Scribbly`, and each asset path gets the same `packages/story_fonts/` prefix. Namespacing stops two packages that both ship a family called `Scribbly` from colliding. The consequence is on the Dart side: ```dart const TextStyle(fontFamily: 'Scribbly', package: 'story_fonts') ``` - The `package` argument makes the constructor store `packages/story_fonts/Scribbly` in `fontFamily`. - The same prefix is applied to every name in `fontFamilyFallback`, so a fallback list passed with `package:` can only name families from that same package. - Code **inside** the package must pass `package: 'story_fonts'` as well; the prefix is not added automatically because the package is the caller. - `ThemeData(fontFamily: 'Scribbly', package: 'story_fonts')` applies the prefixed family to the theme's text themes. Forgetting `package:` is not an error. The engine finds no family called `Scribbly`, falls back to the default font, and the only symptom is the wrong look. ## Option 2: the package ships files, the app declares A package can also carry font files **without** declaring a family. The files live under the package's `lib/` folder, for example `lib/fonts/Scribbly-Regular.ttf`, and are not bundled into any app on their own. An app that wants them declares the family itself: ```yaml flutter: fonts: - family: Scribbly fonts: - asset: packages/story_fonts/fonts/Scribbly-Regular.ttf - asset: packages/story_fonts/fonts/Scribbly-Bold.ttf weight: 700 ``` - The path starts with `packages/<package>/` and **omits `lib/`**, which is implied. - The family now belongs to the app, so it is selected with a plain `TextStyle(fontFamily: 'Scribbly')` — no `package:` argument. - Only the files the app lists are bundled, so an app that needs two faces does not pay for six. ## Choosing between them | | Package declares | App declares from package files | |---|---|---| | Where the family is declared | package `pubspec.yaml` | app `pubspec.yaml` | | Registered family name | `packages/story_fonts/Scribbly` | `Scribbly` | | TextStyle needs `package:` | yes, everywhere | no | | Faces bundled | every face the package declares | only the faces the app lists | | Best for | a design-system package whose widgets use the font | a font library apps pick from | A design-system package with its own widgets fits option 1: its widgets already pass `package:` and apps inherit the same faces. A shared font library fits option 2: each app chooses its faces and keeps short family names. ## Versioning a shared font package Once several apps depend on `story_fonts`, the font becomes a versioned dependency like any other: - A **new face** (adding a Light file) is additive: apps that never request it are unaffected, although option 1 bundles it into every app. - A **redrawn face** changes how existing screens look and wrap text, so treat it as a visible change and roll it out deliberately. - A **renamed family** breaks every `TextStyle` that names the old one — silently, through fallback — so keep family names stable and rename only with a search across all consuming apps. The font's licence travels with the files; the package is the natural place to keep it and to register it for the licences page. ## Pitfalls 1. Writing `package:` when the **app** declared the family from package files: the name becomes `packages/story_fonts/Scribbly`, which matches nothing, and the text falls back. 2. Including `lib/` in the asset path of option 2. 3. Mixing an app family into a `fontFamilyFallback` list passed with `package:` — every entry is prefixed, so the app family is no longer found. 4. Expecting an undeclared file in a package's `lib/` to be bundled automatically: until some pubspec declares it, it is not part of the app.
- What does TextStyle(fontFamily: 'Scribbly', package: 'story_fonts').fontFamily return?`packages/story_fonts/Scribbly`. The constructor rewrites the name so it matches the namespaced family the tool registered for the package. Every `fontFamilyFallback` entry is prefixed the same way, which is why a fallback list passed with `package:` cannot mix in families from the app or another package.
- The app declares Scribbly from packages/story_fonts/fonts/... paths, and text renders in the default font. What is the likely bug?The code passes `package: 'story_fonts'`. When the app declares the family, it is registered under the plain name `Scribbly`, so the prefixed `packages/story_fonts/Scribbly` matches nothing and the engine falls back. Drop the `package:` argument, and check the asset paths omit `lib/`.
saying these in an interview costs you the question
- A package's declared fonts are registered under their plain family names.
- Widgets inside the package can omit the package argument for their own fonts.
- Asset paths to package font files must include the lib/ folder.
- Any font file under a package's lib/ is bundled into the app automatically.
- The package argument is needed even when the app declared the family itself.