Customizing TextTheme for Consistent Material 3 Typography in Flutter
Master TextTheme customization in Flutter for Material 3 typography, mapping display, headline, title, body, and label styles across custom font families.
Published on • November 9, 2026
AI Assistant

Typography establishes hierarchy, readability, and brand character in digital interfaces. In Material 3, Flutter categorizes typography into five distinct role groups—Display, Headline, Title, Body, and Label—with each group divided into large, medium, and small variants.
Configuring a centralized TextTheme ensures every screen adheres to your design system’s font sizes, line heights, and font weights.
The Material 3 Typography Scale
- Display (large/medium/small): Used for short, high-impact numbers or hero promotional text (e.g. 57px, 45px, 36px).
- Headline (large/medium/small): Used for high-emphasis section titles (e.g. 32px, 28px, 24px).
- Title (large/medium/small): Medium-emphasis headers for list items, cards, or dialog titles (e.g. 22px, 16px, 14px).
- Body (large/medium/small): Standard paragraph text for long-form reading (e.g. 16px, 14px, 12px).
- Label (large/medium/small): Utility text for button captions, badge counts, and form labels (e.g. 14px, 12px, 11px).
Defining a Custom TextTheme
You can construct a custom TextTheme using google_fonts or local font assets:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
TextTheme buildAppTextTheme(TextTheme baseTextTheme, Color textColor) {
return GoogleFonts.interTextTheme(baseTextTheme).copyWith(
// Display Roles
displayLarge: GoogleFonts.inter(
fontSize: 57,
fontWeight: FontWeight.w700,
color: textColor,
),
displayMedium: GoogleFonts.inter(
fontSize: 45,
fontWeight: FontWeight.w600,
color: textColor,
),
// Headline Roles
headlineLarge: GoogleFonts.inter(
fontSize: 32,
fontWeight: FontWeight.bold,
color: textColor,
),
headlineMedium: GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w600,
color: textColor,
),
// Title Roles
titleLarge: GoogleFonts.inter(
fontSize: 22,
fontWeight: FontWeight.w600,
color: textColor,
),
titleMedium: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w500,
color: textColor,
),
// Body Roles
bodyLarge: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w400,
height: 1.5, // 24px line height
color: textColor,
),
bodyMedium: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.4,
color: textColor,
),
// Label Roles
labelLarge: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
color: textColor,
),
);
}
Integrating TextTheme into ThemeData
final colorScheme = ColorScheme.fromSeed(seedColor: Colors.indigo);
final baseTheme = ThemeData.light();
final appTheme = ThemeData(
colorScheme: colorScheme,
textTheme: buildAppTextTheme(baseTheme.textTheme, colorScheme.onSurface),
);
Consuming Typography Tokens in Widgets
Always reference text styles from Theme.of(context).textTheme:
class ArticleHeader extends StatelessWidget {
final String title;
final String category;
const ArticleHeader({
super.key,
required this.title,
required this.category,
});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final colorScheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
category.toUpperCase(),
style: textTheme.labelLarge?.copyWith(
color: colorScheme.primary,
),
),
const SizedBox(height: 6),
Text(
title,
style: textTheme.headlineMedium,
),
],
);
}
}
Modifying Hierarchy on the Fly with copyWith
Avoid hardcoding new font sizes when tweaking a single screen. Use .copyWith() to inherit font metrics while adjusting minor attributes:
Text(
'Featured Story',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.amber.shade900,
fontStyle: FontStyle.italic,
),
);
Takeaway: Standardizing typography via ThemeData.textTheme guarantees proper text hierarchy, line heights, and effortless theme switching.