Skip to content
Blog

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

  1. Display (large/medium/small): Used for short, high-impact numbers or hero promotional text (e.g. 57px, 45px, 36px).
  2. Headline (large/medium/small): Used for high-emphasis section titles (e.g. 32px, 28px, 24px).
  3. Title (large/medium/small): Medium-emphasis headers for list items, cards, or dialog titles (e.g. 22px, 16px, 14px).
  4. Body (large/medium/small): Standard paragraph text for long-form reading (e.g. 16px, 14px, 12px).
  5. 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.