app_typography_gen

pub package license: MIT

Generate a typed Text wrapper, a style enum, and the matching text styles for Flutter from one YAML file.

A text style is a font family plus a size, line height, letter spacing and weight. Apps usually hand-write a TextStyle for each one and a widget to apply it. You list the styles once in YAML, grouped by font family, and the generator produces:

  • an enum of every style (e.g. titleRobotoBold),
  • the TextStyles themselves,
  • a Text wrapper that applies them.

You name each style yourself, so the generated names match whatever your design system already calls them.

The core is plain Dart with no flutter or build dependency, so it's easy to test on its own. build_runner and an optional CLI sit on top of it.

Why

A design system's text styles rarely line up with Flutter's built-in TextTheme, so on most projects we ended up hand-writing custom TextStyles - and a pile of boilerplate with them. This generator takes that off your plate. It's inspired by flutter_gen.

Quickstart (build_runner)

  1. Add the dependency:

    dev_dependencies:
      app_typography_gen: ^0.1.0
      build_runner: ^2.4.0
    
  2. Create lib/app.typography.yaml (see the schema below).

  3. Generate:

    dart run build_runner build --delete-conflicting-outputs
    # Flutter projects: flutter pub run build_runner build --delete-conflicting-outputs
    

    This turns lib/app.typography.yaml into lib/app.typography.gen.dart.

  4. Use it:

    import 'app.typography.gen.dart';
    
    AppTypography.titleRobotoBold('Hello');
    AppTypography.bodyRobotoRegular('World', color: Colors.black54);
    

Commit the generated file to source control. Don't edit it by hand; regenerate it when the config changes.

The generated widget

Every constructor renders a Text with the resolved style, disables OS text scaling (TextScaler.noScaling), and exposes the fields you usually reach for:

AppTypography.titleRobotoBold(
  'Heading',
  textAlign: TextAlign.center,   // default TextAlign.start
  color: ColorName.brand,        // falls back to output.default_color
  maxLines: 2,
  overflow: TextOverflow.ellipsis,
  decoration: TextDecoration.underline,
  fontStyle: FontStyle.italic,
  fontWeight: FontWeight.w600,   // overrides the style's weight
  height: 1.2,
);

Schema reference

The config lives under a top-level typography: key, either in a standalone *.typography.yaml file (used by the Builder) or nested inside pubspec.yaml (used by the CLI).

typography:
  output:
    widget: AppTypography             # generated widget class name
    enum: AppTypographyStyle          # generated enum name
    call_site: factory                # factory | enum   (default: factory)
    style_access: text_theme_extension # text_theme_extension | static_class | widget
    default_color: ColorName.gray900  # a Dart expression, copied as-is
    imports:                          # imports the generated file needs
      - package:my_app/gen/colors.gen.dart
      - package:my_app/gen/fonts.gen.dart

  families:                           # font_family is shared by the styles below
    Roboto:
      font_family: FontFamily.roboto  # a Dart expression, copied as-is
      styles:
        titleRobotoBold:             # the key is the generated style name
          size: 72
          line_height: 88             # optional; emitted as height: 88 / 72
          tracking: -0.8              # optional; becomes letterSpacing
          weight: 700                 # optional; 100..900 -> FontWeight.w700
        bodyRoboto:
          size: 16                    # size is the only required field
          line_height: 24
    RobotoSlab:
      font_family: FontFamily.robotoSlab
      styles:
        headingRobotoSlab:
          size: 56
          line_height: 68
          weight: 600

Keys

Key Required Meaning
output.widget yes Generated widget class name.
output.enum yes Generated enum name.
output.call_site no (factory) factory -> one named constructor per style; enum -> one constructor taking a style: argument.
output.style_access no (text_theme_extension) Where the styles live: text_theme_extension -> getters on a TextTheme extension (needs a BuildContext); static_class -> static const fields on a separate class; widget -> static const fields on the widget class, so AppTypography.titleRobotoBold is the TextStyle (requires call_site: enum).
output.default_color yes Dart expression used as the widget's default color. Copied through unchanged.
output.imports no ([]) Imports the generated file needs (for default_color, font_family, etc.). Copied through unchanged.
families.<Name>.font_family yes Dart expression for fontFamily:, shared by the family's styles. Copied through unchanged.
families.<Name>.styles.<name> yes A style. The key is the generated name and must be a valid Dart identifier, unique across all families.
...styles.<name>.size yes Font size (number).
...styles.<name>.line_height no Line height in pixels; emitted as height: line_height / size.
...styles.<name>.tracking no Letter spacing.
...styles.<name>.weight no FontWeight number, 100..900 in steps of 100.
...styles.<name>.font_family no Overrides the family's font_family for this one style.

Emission rules

  • The style key is emitted as the name, exactly as written.
  • Only the fields you set end up in the TextStyle. A style with just a size produces TextStyle(fontFamily: ..., fontSize: ...) and nothing else.
  • line_height is in pixels and emitted as the ratio height: <line_height> / <size>.
  • tracking becomes letterSpacing; weight becomes fontWeight: FontWeight.w<n>.
  • font_family, default_color, and imports are copied straight through. The generator never makes up package paths.

Call-site styles

factory (default): a named constructor per style.

AppTypography.titleRobotoBold('x');

enum: one constructor plus the style enum.

AppTypography('x', style: AppTypographyStyle.titleRobotoBold);

Getting the raw TextStyle

text_theme_extension exposes the styles as TextTheme getters (context.textTheme.titleRobotoBold) and static_class as AppTypographyStyles.titleRobotoBold. With style_access: widget (which needs call_site: enum) the styles are static const fields on the widget class itself:

final style = AppTypography.titleRobotoBold;              // a TextStyle
const AppTypography('x', style: AppTypographyStyle.titleRobotoBold); // the widget

Programmatic use

import 'package:app_typography_gen/app_typography_gen.dart';

final dartSource = generateTypography(yamlString);

Additional information

Libraries

app_typography_gen
Generate a Text wrapper widget, a style enum and the text styles for Flutter from one YAML typography config.
builder