Typography, Text Styles and Themes in Flutter

Build and publish a Flutter-based app for the App Store and Google Play — updated for 2026 to reflect the current Material 3 typography system.

When this article was first written, Flutter's TextTheme used naming conventions inherited from the 2018 Material Design guidelines, themselves a partial update on the 2014 spec. The sizes, weights, and spacing still largely reflected that older era. Since then, Flutter has completed its migration to Material 3 (also called Material You), and the typography system has been rebuilt from the ground up to match it.

TL:DR – Flutter's typography system has been substantially overhauled since this series began. The old headline1headline6, bodyText1, bodyText2, subtitle1, subtitle2, caption, overline, and button style names are now fully deprecated and removed. The current TextTheme follows Material 3, with five semantic categories — Display, Headline, Title, Label, Body — each offering Large, Medium, and Small variants, giving you fifteen named styles in total. If your codebase still references headline1 or bodyText2, it will no longer compile without migration.

What's changed in 2026

Variable font support has also matured significantly. The FontVariation class conforms to the OpenType font variables spec, letting you animate or adjust weight, width, and other axes at runtime. Combined with the google_fonts package on pub.dev, it is now straightforward to use variable Google Fonts directly in your app with fine-grained control over their axes.

Styled text

The dummy text on the home page of the app is themed. In the original version it was styled as headline6 — the Material 3 equivalent is titleLarge. The official Material 3 type scale documentation is the definitive reference for understanding how these roles map to visual hierarchy.

Material 3 type scale for Flutter

The most important discipline when using Flutter's text system is consistency. The Material Component widgets (MDC-Flutter) bake in padding, sizing, and spacing assumptions based on these roles. If you override them arbitrarily, the result looks subtly wrong to users even if they can't articulate why — and that friction shows up in reviews.

The table below reflects the current Material 3 TextTheme as implemented in Flutter. The default typeface remains Roboto on Android; on iOS, SF Pro is used via the platform default.

NAMESIZEWEIGHTLINE HEIGHT
displayLarge 57.0 regular 64.0
displayMedium 45.0 regular 52.0
displaySmall 36.0 regular 44.0
headlineLarge 32.0 regular 40.0
headlineMedium 28.0 regular 36.0
headlineSmall 24.0 regular 32.0
titleLarge 22.0 regular 28.0
titleMedium 16.0 medium 24.0
titleSmall 14.0 medium 20.0
bodyLarge 16.0 regular 24.0
bodyMedium 14.0 regular 20.0
bodySmall 12.0 regular 16.0
labelLarge 14.0 medium 20.0
labelMedium 12.0 medium 16.0
labelSmall 11.0 medium 16.0

Note: This uses the Roboto font rendered in a browser, which is only an approximation of how these styles will appear in a real app on device.

Migrating from the old names

If you are working with an existing codebase, here is a quick reference for the most common name changes. The old names were deprecated in Flutter 3.1 and have since been removed entirely.

  • headline1displayLarge
  • headline2displayMedium
  • headline3displaySmall
  • headline4headlineMedium
  • headline5headlineSmall
  • headline6titleLarge
  • subtitle1titleMedium
  • subtitle2titleSmall
  • bodyText1bodyLarge
  • bodyText2bodyMedium
  • captionbodySmall
  • buttonlabelLarge
  • overlinelabelSmall

Running dart fix --apply in your project will handle many of these renames automatically, though you should review the output carefully since the semantic intent of some styles has shifted slightly alongside the rename.

Add each text style to the main page to see how they look

The Scaffold provides APIs for showing drawers, snack bars, and bottom sheets, and already carries a background colour and an AppBar. Let's add a ListView with ExpansionTile widgets to preview every text style on a real device. The approach is the same as before — but the style references are updated throughout.


body: Padding(
  padding: EdgeInsets.all(16.0),
  child: ListView(
    children: [
      ExpansionTile(
        leading: Icon(Icons.text_fields),
        title: Text('displayLarge'),
        subtitle: Text('textTheme'),
        children: [
          Text(
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
            style: Theme.of(context).textTheme.displayLarge,
          )
        ],
      ),
    ],
  ),
),

Repeat the ExpansionTile block for each of the fifteen styles, then collapse them into a single tile to see the full scale at a glance:


ExpansionTile(
  leading: Icon(Icons.text_fields),
  title: Text('textTheme'),
  subtitle: Text('All 15 Material 3 styles'),
  children: [
    Text('Lorem!',
      style: Theme.of(context).textTheme.displayLarge,
    ),
    Text('Lorem ipsum',
      style: Theme.of(context).textTheme.displayMedium,
    ),
    Text('Lorem ipsum',
      style: Theme.of(context).textTheme.displaySmall,
    ),
    Text('Lorem ipsum dolor sit',
      style: Theme.of(context).textTheme.headlineLarge,
    ),
    Text('Lorem ipsum dolor sit amet',
      style: Theme.of(context).textTheme.headlineMedium,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur',
      style: Theme.of(context).textTheme.headlineSmall,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
      style: Theme.of(context).textTheme.titleLarge,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget justo.',
      style: Theme.of(context).textTheme.titleMedium,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget justo.',
      style: Theme.of(context).textTheme.titleSmall,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget justo non lacus.',
      style: Theme.of(context).textTheme.bodyLarge,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget justo non lacus.',
      style: Theme.of(context).textTheme.bodyMedium,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget justo non lacus.',
      style: Theme.of(context).textTheme.bodySmall,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget justo non lacus.',
      style: Theme.of(context).textTheme.labelLarge,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget justo non lacus.',
      style: Theme.of(context).textTheme.labelMedium,
    ),
    Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget justo non lacus.',
      style: Theme.of(context).textTheme.labelSmall,
    ),
  ],
),

Flutter Material Components Android light appearance ListView of all textThemes   Flutter Material Components Android dark appearance ListView of all textThemes

The ListView and its ExpansionTile children handle layout automatically. Seeing every style rendered on a real device is the fastest way to develop an intuition for which role fits which piece of UI content.

Variable fonts and Google Fonts

One of the more significant improvements in recent Flutter versions is first-class support for variable fonts via the FontVariation class. Variable fonts encode multiple style variations — weight, width, slant, optical size, and more — in a single font file, which reduces bundle size and enables smooth animated transitions between styles.

To use a variable font, declare it in pubspec.yaml as usual, then pass FontVariation instances to the fontVariations parameter of TextStyle:


Text(
  'Variable weight example',
  style: TextStyle(
    fontFamily: 'RobotoFlex',
    fontVariations: [
      FontVariation('wght', 350.0),
    ],
  ),
)

If you prefer to pull fonts directly from Google Fonts rather than bundling them, the google_fonts package on pub.dev makes this straightforward. You can also use the Google Fonts type tester to preview variable font capabilities before committing to a choice. Be mindful of network font loading in production — the package supports bundling fonts as assets for offline reliability.

Add a Card

Cards are a fundamental surface component in Material 3, elevated above the background to contain related content and actions. Material 3 introduced three card variants — Elevated, Filled, and Outlined — giving you more expressive options than the single card style available previously.

Note that all appearance settings live outside this block; this is purely the card structure within its expansion tile. Also note that FlatButton and ButtonBar are both removed in current Flutter — replace them with TextButton and a Row or OverflowBar respectively:


ExpansionTile(
  leading: Icon(Icons.crop_7_5),
  title: Text('Cards'),
  subtitle: Text('Elevated, Filled, Outlined'),
  children: [
    Card(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const ListTile(
            leading: Icon(Icons.explicit),
            title: Text('Kometenmelodie 2'),
            subtitle: Text('Kraftwerk'),
          ),
          OverflowBar(
            children: [
              TextButton(
                child: const Text('BUY'),
                onPressed: () {/* ... */},
              ),
              TextButton(
                child: const Text('LISTEN'),
                onPressed: () {/* ... */},
              ),
            ],
          ),
        ],
      ),
    ),
    Card(
      child: Image.asset('assets/ezone-512-transparent.png',
        fit: BoxFit.fill,
      ),
    ),
  ],
),

In Material 3, surface tones replace the elevation overlay approach used in dark themes under Material 2. Rather than lightening a surface by blending white at a given opacity, Material 3 uses tonal surface colours derived from your seed colour. This means dark-theme elevation is more consistent and easier to reason about — the applyElevationOverlayColor property that caused confusion in the original version of this article is no longer the primary mechanism.

Brand colour

The logo image in the card above carries the brand colour. Material 3's colour system is built around a seed colour from which an entire harmonious palette — primary, secondary, tertiary, error, and their container variants — is generated automatically via the ColorScheme.fromSeed constructor. This makes it straightforward to derive a full light and dark theme from a single brand colour, ensuring the logo always sits coherently within the palette rather than clashing with it.

Flutter Material Components Android light appearance with a Card   Flutter Material Components Android dark appearance with a Card

Finding and fixing theme and colour problems with the UI

Look carefully at the screenshots and there is a visible issue: the expansion tile trailing icon is invisible in dark appearance but fine in light appearance. This points to a colour inheritance problem somewhere in the theme.

The most efficient way to track this down is the Flutter Inspector in your IDE. Open the inspector, select Widget Tree, and enable Select Widget Mode using the crosshair button. Tap the area where the missing element should appear and the tree will navigate directly to it, highlighting the widget on the running app surface simultaneously. From there you can inspect the widget's resolved colours and trace where they originate.

In this case the missing element is a TextSpan inside the ExpansionTile widget. A search for flutter expansiontile trailing icon dark theme on Stack Overflow surfaces several relevant threads — the consistent solution is to set unselectedWidgetColor in your dark ThemeData. Add it to the dark theme block and hot-reload to confirm the fix.

This kind of diagnostic work is genuinely useful beyond the immediate fix. Understanding how MDC-Flutter components inherit from ThemeData makes your theme definitions more robust and reduces the chance of similar surprises when you add new components later.

Flutter Material Components Android dark appearance Select Widgets mode   Flutter Material Components Android dark appearance fixed

Continue adding components

As a way to build familiarity with the full range of Material component widgets, keep adding one of each to the page so you can see how they all behave together. With Material 3 now the default in Flutter, many components have updated visuals — rounded corners, tonal fills, and updated motion — so it is worth reviewing even components you have used before to see how they have changed.