Typography is one of the fastest ways to make a Flutter app feel polished and on-brand. But fonts are also one of the most common sources of beginner frustration — because a single indentation mistake in pubspec.yaml, a wrong family name in Dart, or a missing full restart can make your font silently fall back to the default with no error message.
This guide covers both approaches Flutter supports — adding local font files to your project and using the google_fonts package — with complete copy-paste examples, a side-by-side comparison of when to use each, every common mistake beginners make with fixes, and a full FAQ. By the end you will know exactly how to set up any font and debug it when it does not appear.
- A working Flutter project — if you are starting fresh, follow our Flutter Tutorial for Beginners first
- Basic understanding of
pubspec.yaml— see our pubspec.yaml Complete Guide if this file is new to you - Flutter SDK installed and
flutter doctorshowing no critical errors
1. Local Fonts vs Google Fonts — Which Should You Use?
Before writing a single line of code, it helps to know which approach fits your situation. A helpful way to think about it: local fonts are best when you want full control and offline bundling, while google_fonts is best when you want faster setup and easy access to over a thousand font families. Here is the full comparison:
| Factor | 📁 Local Font Files | 📦 google_fonts Package |
|---|---|---|
| Setup time | Longer — download files, add to project, configure YAML | Fast — add one dependency, one import, one line of code |
| Font library | Any font you have a license for (Google, paid, custom brand) | 1,000+ Google Fonts families only |
| Offline support | ✅ Always available — bundled in the app binary | ⚠️ Fetched at runtime by default; needs extra setup to bundle |
| Font flash on load | ❌ Never — font is already in the binary | ⚠️ Possible on first load if not bundled |
| App size | Increases by font file size (typically 50–300 KB per file) | Small overhead from package; fonts cached after first download |
| Brand/custom fonts | ✅ Best choice — full control | ❌ Only Google Fonts |
| Best for | Production apps, brand fonts, offline-first, paid fonts | Prototyping, personal projects, fast iteration |
If you are learning or prototyping — use
google_fonts. It takes 5 minutes and gets you any of 1,000 fonts with one line of code. If you are building a real app for a client or brand — use local fonts for full control, offline reliability, and no runtime font fetching.
2. Adding Local Fonts: Step-by-Step
Flutter supports .ttf, .otf, and .ttc font files. Do not use .woff or .woff2 — these are web formats and are not supported on desktop platforms.
Step 1 — Create a fonts folder and add your files
Create a fonts/ folder at the root of your project (next to lib/ and pubspec.yaml, not inside lib/). Download your font files and place them there:
my_app/
├── android/
├── ios/
├── lib/
│ └── main.dart
├── fonts/ ← create this folder at the project root
│ ├── Poppins-Regular.ttf
│ ├── Poppins-Medium.ttf
│ ├── Poppins-Bold.ttf
│ └── Poppins-Italic.ttf
└── pubspec.yaml
Step 2 — Declare the fonts in pubspec.yaml
Open pubspec.yaml and add the fonts section under the flutter key. Indentation is critical — YAML uses spaces (not tabs) and the nesting must be exact. The family value is the name you will use in your Dart code — it does not have to match the filename:
flutter:
uses-material-design: true
fonts:
- family: Poppins # ← the name you use in Dart — not the filename
fonts:
- asset: fonts/Poppins-Regular.ttf
- asset: fonts/Poppins-Medium.ttf
weight: 500
- asset: fonts/Poppins-Bold.ttf
weight: 700
- asset: fonts/Poppins-Italic.ttf
style: italic
- Writing
assets:instead ofasset:(no ‘s’) for font file entries - Placing
fonts:at the wrong indentation level — it must be insideflutter: - Using tabs instead of spaces — YAML requires spaces only
- Forgetting the
-dash beforefamily:
Step 3 — Run flutter pub get and fully restart
# After editing pubspec.yaml, always run:
flutter pub get
# Then FULLY restart the app (not just hot reload):
# Press q to stop, then:
flutter run
Font and asset registration happens at app startup when Flutter reads
pubspec.yaml. A hot reload does not re-read this file. If you add a font and hot reload, your font will not appear — you need a full stop and restart every time you change pubspec.yaml. See our Hot Reload vs Hot Restart guide for the full explanation.
Step 4 — Use the font in a widget
Use the exact family string from pubspec.yaml as the fontFamily value in TextStyle. The string is case-sensitive — 'poppins' and 'Poppins' are different:
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: FontDemoPage()));
}
class FontDemoPage extends StatelessWidget {
const FontDemoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Custom Font Demo')),
body: const Padding(
padding: EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Poppins Regular',
style: TextStyle(
fontFamily: 'Poppins', // ← must match family: in pubspec.yaml exactly
fontSize: 22,
),
),
SizedBox(height: 12),
Text(
'Poppins Medium',
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 22,
fontWeight: FontWeight.w500, // maps to weight: 500
),
),
SizedBox(height: 12),
Text(
'Poppins Bold',
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 22,
fontWeight: FontWeight.w700, // maps to weight: 700
),
),
SizedBox(height: 12),
Text(
'Poppins Italic',
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 22,
fontStyle: FontStyle.italic, // maps to style: italic
),
),
],
),
),
);
}
}
3. Applying Local Fonts App-Wide with ThemeData
Applying the font to every widget individually is tedious and easy to forget. The right approach is to set fontFamily in your app’s ThemeData so every Text widget in the entire app inherits it automatically. Flutter’s docs recommend this pattern and it is the same family name declared in pubspec.yaml:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
// Set the app-wide font — every Text widget uses this by default
fontFamily: 'Poppins',
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')), // ← uses Poppins automatically
body: const Padding(
padding: EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// No fontFamily needed on any of these — inherited from ThemeData
Text('This text uses Poppins', style: TextStyle(fontSize: 20)),
SizedBox(height: 12),
Text(
'This one too — inherited automatically',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
SizedBox(height: 12),
// You can still override for a single widget if needed
Text(
'This one uses a different font explicitly',
style: TextStyle(fontFamily: 'Roboto', fontSize: 16),
),
],
),
),
);
}
}
4. Multiple Weights and Italic Styles
A very common beginner mistake is using fontWeight: FontWeight.bold and expecting Flutter to automatically render bold — but if you did not include the bold font file and declare it with weight: 700 in pubspec.yaml, Flutter will use a faux-bold simulation that looks slightly off and varies by platform. Always include the real font files for every weight and style you plan to use.
Here is the full pubspec.yaml declaration for a family with multiple weights, plus the matching Dart code showing how each weight maps to its file:
# pubspec.yaml — full weight + style declaration
flutter:
fonts:
- family: Inter
fonts:
- asset: fonts/Inter-Thin.ttf
weight: 100
- asset: fonts/Inter-ExtraLight.ttf
weight: 200
- asset: fonts/Inter-Light.ttf
weight: 300
- asset: fonts/Inter-Regular.ttf
weight: 400
- asset: fonts/Inter-Medium.ttf
weight: 500
- asset: fonts/Inter-SemiBold.ttf
weight: 600
- asset: fonts/Inter-Bold.ttf
weight: 700
- asset: fonts/Inter-ExtraBold.ttf
weight: 800
- asset: fonts/Inter-Italic.ttf
weight: 400
style: italic
- asset: fonts/Inter-BoldItalic.ttf
weight: 700
style: italic
// Dart — each FontWeight maps to the file declared at that weight
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Inter Thin', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w100)),
Text('Inter ExtraLight', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w200)),
Text('Inter Light', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w300)),
Text('Inter Regular', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w400)),
Text('Inter Medium', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w500)),
Text('Inter SemiBold', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w600)),
Text('Inter Bold', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w700)),
Text('Inter ExtraBold', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w800)),
Text('Inter Italic', style: TextStyle(fontFamily: 'Inter', fontStyle: FontStyle.italic)),
Text('Inter Bold Italic', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.w700, fontStyle: FontStyle.italic)),
],
)
Each font file adds to your app’s binary size — typically 50–200 KB per file. If you only use Regular and Bold, only declare those two. Don’t add all 10 weights just because they exist.
5. Using the google_fonts Package
The google_fonts package gives you access to 1,000+ Google Fonts families without downloading any files manually. It supports runtime HTTP fetching with automatic caching, asset bundling for offline use, and applying a font to a single Text widget or to a full TextTheme.
Step 1 — Add the dependency
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
google_fonts: ^8.0.2 # check pub.dev/packages/google_fonts for the latest version
# Then run:
flutter pub get
Step 2 — Use a Google Font in a widget
Import the package and call the font by its camelCase method name. The method accepts all the same parameters as TextStyle — fontSize, fontWeight, color, letterSpacing, and so on:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(const MaterialApp(home: GoogleFontDemoPage()));
}
class GoogleFontDemoPage extends StatelessWidget {
const GoogleFontDemoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Google Fonts Demo',
style: GoogleFonts.poppins(fontWeight: FontWeight.w600),
),
),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Poppins Regular',
style: GoogleFonts.poppins(fontSize: 22),
),
const SizedBox(height: 12),
Text(
'Poppins Bold',
style: GoogleFonts.poppins(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(
'Lato — a different font family entirely',
style: GoogleFonts.lato(fontSize: 22, color: Colors.deepPurple),
),
const SizedBox(height: 12),
Text(
'Roboto Mono — great for displaying code',
style: GoogleFonts.robotoMono(fontSize: 18),
),
const SizedBox(height: 12),
Text(
'Playfair Display — elegant for headings',
style: GoogleFonts.playfairDisplay(
fontSize: 22,
fontStyle: FontStyle.italic,
),
),
],
),
),
);
}
}
6. Applying Google Fonts App-Wide
Just like with local fonts, you can apply a Google Font to your entire app using ThemeData. The google_fonts package provides a textTheme() method that generates a complete Material text theme with your chosen font applied to every text role:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
// Option A: Apply one font to the entire text theme (replaces all roles)
textTheme: GoogleFonts.poppinsTextTheme(),
// Option B: Preserve existing theme sizes/styles but change only the font
// textTheme: GoogleFonts.poppinsTextTheme(ThemeData.light().textTheme),
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// All inherit Poppins from the theme — no GoogleFonts call needed here
Text('Display Large', style: textTheme.displayLarge),
Text('Headline Medium', style: textTheme.headlineMedium),
Text('Title Large', style: textTheme.titleLarge),
Text('Body Large', style: textTheme.bodyLarge),
Text('Body Small', style: textTheme.bodySmall),
],
),
),
);
}
}
A common design pattern is to use one font for headings (like Playfair Display) and another for body copy (like Inter or Lato). Apply
GoogleFonts.playfairDisplay() selectively to displayLarge and headlineMedium in your textTheme, while leaving body styles with a different font — you get full control over every text role.
7. Bundling Google Fonts for Offline Use
The default runtime-fetch behaviour is fine for development, but for production apps it creates two problems: a visible font flash on first load when the font hasn’t been cached yet, and broken typography if the user is offline. The package supports both runtime fetching and asset bundling — bundling is the recommended approach for release builds.
How to bundle Google Fonts as assets
# Step 1: Download the exact font files from fonts.google.com
# IMPORTANT: Do NOT rename the files — google_fonts detects them by original filename
# Step 2: Place them in an assets/fonts folder at your project root
my_app/
├── assets/
│ └── fonts/
│ ├── Poppins-Regular.ttf ← original filename preserved
│ ├── Poppins-Bold.ttf
│ └── Poppins-Italic.ttf
└── pubspec.yaml
# pubspec.yaml — declare under assets (NOT under fonts)
# google_fonts reads these as assets, not as Flutter font declarations
flutter:
uses-material-design: true
assets:
- assets/fonts/ # ← the package scans this folder and matches by filename
No changes are needed in your Dart code. The google_fonts package automatically detects the bundled files by filename and uses them instead of fetching from the network. Your existing GoogleFonts.poppins() calls work exactly as before — the switch to local files is invisible.
Avoiding font flash without bundling: pendingFonts()
If you cannot bundle the fonts but still want to prevent visible font swapping on first load, the package provides GoogleFonts.pendingFonts(). Call it in main() before runApp() to pre-fetch the fonts you need and wait for them to finish loading:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Pre-fetch and cache the fonts before the first frame renders.
// This prevents the default font appearing briefly before switching to your font.
await GoogleFonts.pendingFonts([
GoogleFonts.poppins(),
GoogleFonts.poppins(fontWeight: FontWeight.bold),
GoogleFonts.poppins(fontStyle: FontStyle.italic),
]);
runApp(const MyApp());
}
The
google_fonts package matches font files to font families by their original filename (e.g. Poppins-Bold.ttf). If you rename a file, the package will not recognise it and will fall back to fetching from the network. Always keep the original Google Fonts filename exactly as downloaded.
8. Common Beginner Mistakes
Mistake 1: Wrong YAML key — assets instead of asset
# ❌ Wrong — 'assets' (with 's') is for images/files, not font file entries
flutter:
fonts:
- family: Poppins
fonts:
- assets: fonts/Poppins-Regular.ttf # ← wrong key, font silently ignored
# ✅ Correct — 'asset' (no 's') for each individual font file entry
flutter:
fonts:
- family: Poppins
fonts:
- asset: fonts/Poppins-Regular.ttf # ← correct
Mistake 2: fontFamily string doesn’t exactly match the YAML family name
# pubspec.yaml:
fonts:
- family: Poppins # declared as 'Poppins'
# ❌ Wrong — Flutter won't find the font, silently falls back to default
Text('Hello', style: TextStyle(fontFamily: 'poppins')) // wrong case
Text('Hello', style: TextStyle(fontFamily: 'POPPINS')) // wrong case
Text('Hello', style: TextStyle(fontFamily: 'Poppins-Regular')) // wrong — use family name, not filename
# ✅ Correct — must match family: value exactly, character for character
Text('Hello', style: TextStyle(fontFamily: 'Poppins'))
Mistake 3: Hot reloading after pubspec.yaml changes
# ❌ Wrong — hot reload after editing pubspec.yaml
# 1. Add font to pubspec.yaml
# 2. Press r (hot reload)
# 3. Font still shows as default — confusing!
# ✅ Correct — always run pub get then do a full restart
flutter pub get
# Press q to stop the app, then:
flutter run
# The font is now registered and loads on the next startup
Mistake 4: Wrong indentation in pubspec.yaml
# ❌ Wrong — fonts: is at the top level, outside the flutter: block
flutter:
uses-material-design: true
fonts: # ← wrong indentation, not inside flutter:
- family: Poppins
fonts:
- asset: fonts/Poppins-Regular.ttf
# ✅ Correct — fonts: must be indented 2 spaces inside flutter:
flutter:
uses-material-design: true
fonts: # ← correct: 2 spaces inside flutter:
- family: Poppins
fonts:
- asset: fonts/Poppins-Regular.ttf
Mistake 5: Using FontWeight.bold without declaring the bold font file
# ❌ Wrong — only Regular declared, but bold weight used in Dart
flutter:
fonts:
- family: Inter
fonts:
- asset: fonts/Inter-Regular.ttf # only Regular declared
# In Dart: FontWeight.bold requests w700 → Flutter fakes it → looks slightly wrong
Text('Bold text', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.bold))
# ✅ Correct — declare the actual bold file with weight: 700
flutter:
fonts:
- family: Inter
fonts:
- asset: fonts/Inter-Regular.ttf
- asset: fonts/Inter-Bold.ttf
weight: 700 # ← now FontWeight.bold uses the real bold file
Mistake 6: google_fonts version incompatible with current Flutter SDK
# If you see errors like "type FontFeature not found" or compile errors
# after upgrading google_fonts:
# Step 1: Check your current Flutter version
flutter --version
# Step 2: Align your Flutter SDK and google_fonts version
# Check compatible versions at pub.dev/packages/google_fonts
# Step 3: Update Flutter to stable channel if needed
flutter upgrade
# Step 4: Clean and re-fetch everything
flutter clean
flutter pub get
9. Beginner FAQ
A: For most beginners, the
google_fonts package is the fastest start — add the dependency, import the package, and call GoogleFonts.fontName(). No downloading files, no pubspec.yaml font declarations needed. For brand or paid fonts that are not on Google Fonts, use the local font files approach.
A: Put the
.ttf or .otf file in your project (typically a fonts/ folder at the root), declare it under flutter > fonts in pubspec.yaml with a family name and asset path, run flutter pub get, fully restart the app, then use the family name in TextStyle(fontFamily: 'YourFontName') or set it in ThemeData(fontFamily: 'YourFontName') to apply it app-wide.
A: The four most common causes are: (1) wrong
fontFamily string in Dart — it must exactly match the family: value in pubspec.yaml, case-sensitive; (2) wrong YAML indentation or assets instead of asset; (3) you hot reloaded instead of fully restarting after changing pubspec.yaml; (4) the font file path in YAML does not match the actual file location. Run flutter pub get and do a full stop-and-restart first.
A: Flutter supports
.ttf, .otf, and .ttc. It does not support .woff or .woff2 on desktop platforms. Always use .ttf or .otf for maximum compatibility across Android, iOS, web, and desktop targets.
A: For local fonts, set
fontFamily: 'YourFont' in ThemeData inside MaterialApp. For Google Fonts, set textTheme: GoogleFonts.yourFontTextTheme() in ThemeData. Both approaches make every Text widget in the app inherit the font automatically without needing to specify it on each widget.
A: For development and prototyping, runtime fetching is fine. For any production app, bundle the font files under
flutter.assets in pubspec.yaml. This prevents font flashing on first load, ensures the font works offline, and makes your app behave consistently without a network connection. Do not rename the files — the package detects them by their original filename.
A: Yes, and it is a common design pattern. With local fonts, declare both families in
pubspec.yaml and apply each by name where needed. With google_fonts, call a different GoogleFonts.fontName() method on the text you want to differ. You can also build a custom TextTheme that assigns different fonts to different text roles — displayLarge, headlineMedium, bodyLarge, etc.
10. Related Posts
| Post | Why it’s relevant |
|---|---|
| Understanding pubspec.yaml in Flutter: A Beginner’s Complete Guide | Font setup lives entirely in pubspec.yaml — read this guide if indentation or structure is still confusing you. |
| Flutter Tutorial for Beginners: From Install to First App | Set up your project correctly from the start — makes font setup much smoother since your folder structure is already clean. |
| Flutter Widgets Explained: Stateless vs Stateful | Understanding StatelessWidget and StatefulWidget helps you know where to put your font styling code and when it rebuilds. |
| Flutter Layout Made Easy: Row, Column, Flex and Expanded | Once your fonts are set, layout is the next thing to master — build the structure that actually displays your typography. |
| Hot Reload vs Hot Restart in Flutter | Essential reading after this guide — explains exactly why hot reload is not enough after pubspec.yaml changes and when you must do a full restart. |
