Flutter integration
Custom Flutter icons that keep their codepoints
GlyphPact compiles an SVG directory into an OpenType font plus a tree-shakeable const IconData class, and records every codepoint in a file you commit. Adding icons later does not renumber the ones your widgets already reference.
- Python 3.10 or newer
- Dart 3 or newer, to consume the generated provider
- No Node toolchain
SVG sourcesOpenType fontlock fileconst IconData
Same inputs. Same codepoints. Reviewable output.
Quick start
From a directory of SVGs to a working Icon widget
Install GlyphPact
uv tool install glyphpactInstalls the latest stable release from PyPI, currently v1.0.1. Already installed? Run
uv tool upgrade glyphpact. Release notes.Compile the pack into your app
Sources are discovered recursively. GlyphPact takes ownership of the output directory and will not overwrite a non-empty directory that lacks its marker, so point it somewhere generated.
glyphpact assets/icons \ --output lib/generated/app_icons \ --name AppIcons
Register the font in pubspec.yaml
This step is yours: GlyphPact writes the font but does not edit your pubspec. Asset paths are relative to the
pubspec.yamlthat declares them, and thefamilymust equal the configured font family.flutter: fonts: - family: AppIcons fonts: - asset: lib/generated/app_icons/fonts/AppIcons.otfUse the generated constants
Generated
IconDatacarries no label. Supply a localizedsemanticLabelfor meaningful icons, use a labelledIconButton, or exclude decorative ones from semantics.import 'package:my_app/generated/app_icons/app_icons.dart'; // Meaningful icon: give it a localized label. Icon( AppIcons.back, semanticLabel: 'Back', ) // Decorative icon: keep it out of the semantics tree. ExcludeSemantics( child: Icon(AppIcons.verified), )
Generated Dart
What the provider looks like
Ordinary Dart, checked into your repository, reviewable in a pull request. Every constant records the source file it came from and any licence you declared for it.
// GENERATED CODE - DO NOT MODIFY BY HAND.
// Generated by GlyphPact 1.0.1.
import 'package:flutter/widgets.dart' as flutter;
/// Constant [flutter.IconData] values backed by the AppIcons font.
@flutter.staticIconProvider
abstract final class AppIcons {
static const String _fontFamily = 'AppIcons';
static const String? _fontPackage = null;
/// Source: arrows/back.svg
/// License: MIT
static const flutter.IconData back = flutter.IconData(
0xE000,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
matchTextDirection: true,
);
/// Source: status/verified.svg
/// License: MIT
static const flutter.IconData verified = flutter.IconData(
0xE001,
fontFamily: _fontFamily,
fontPackage: _fontPackage,
);
}Tree shaking, precisely
The provider is emitted the way Flutter's icon subsetting requires: anabstract final class annotated@flutter.staticIconProvider, containing onlyconst IconData values whose codepoint, family, and package are compile-time constants.
That annotation is a promise the analyzer enforces, namely that noIconData in the class is constructed dynamically. Flutter needs that guarantee before it can drop unused glyphs from the font.
What GlyphPact does and does not control. It emits a provider that satisfies the contract. Whether subsetting actually runs is your build's decision: it applies to release builds and is disabled by --no-tree-shake-icons. ConstructingIconData from a variable anywhere else in your app is the usual reason subsetting gets switched off.
Source control
Commit the whole owned output
The lock is the codepoint ABI. Losing it can reassign icons even when no source file changed, so it belongs in Git with everything else.
# GlyphPact leaves this coordination file beside the output directory. lib/generated/.app_icons.glyphpact.lock
Shipping icons from a Dart package
When the font lives in a package rather than the application, pass the package name. The generated IconData constants then carryfontPackage, which is how Flutter resolves the asset from a dependency.
glyphpact assets/icons \ --output lib/generated/app_icons \ --name AppIcons \ --font-package my_icon_package
Keep the family entry in the package's ownpubspec.yaml equal to the configured font family, or Flutter will not find the glyphs at runtime.
Icons that genuinely need more than one alpha value
A single icon-font glyph stores one coverage mask, so it cannot hold two different opacities at once. Rather than silently flattening such an icon, GlyphPact can represent it as up to eight ordered solid-alpha outlines, each emitted into an auxiliary font family at the icon's unchanged codepoint.
Opt in per icon, in the config:
{
"icons": {
"status/verified_layers.svg": {
"partialAlpha": {
"mode": "layers",
"fallback": "silhouette"
}
}
}
}flutter:
fonts:
- family: AppIcons
fonts:
- asset: lib/generated/app_icons/fonts/AppIcons.otf
- family: AppIcons Layer 1
fonts:
- asset: lib/generated/app_icons/layer_fonts/layer_1.otf
- family: AppIcons Layer 2
fonts:
- asset: lib/generated/app_icons/layer_fonts/layer_2.otfThe generated Dart adds a layered widget and layer descriptors, and the plain IconData remains available as the fallback you explicitly chose:
AppIconsLayeredIcon( AppIconsLayers.verifiedLayers, size: 24, color: const Color(0xFF222222), semanticLabel: 'Verified', )
The report records every layer's paint order, opacity, family, file, codepoint, and bounds. Seethe runnable layered example.
Check in a config instead of memorising flags
For anything you build more than once, a JSON config beats a shell command. Paths resolve relative to the config file, CLI flags override config values, and jobs: 0 selects up to eight bounded worker processes.
{
"$schema": "https://raw.githubusercontent.com/omar-hanafy/glyphpact/main/schema/icon-font-config.schema.json",
"input": "assets/icons",
"output": "lib/generated/app_icons",
"fontFamily": "AppIcons",
"className": "AppIcons",
"fontPackage": null,
"startCodepoint": "0xE000",
"unitsPerEm": 1000,
"padding": 0,
"clipToViewBox": true,
"policy": {
"lossy": "error",
"unrepresentable": "error"
},
"jobs": 0,
"icons": {
"arrows/back.svg": {
"name": "back",
"matchTextDirection": true,
"author": "Your team",
"license": "MIT"
}
}
}Per-icon entries are also where provenance goes.author, license, sourceUrl, andcopyright flow into the generatedATTRIBUTION.md, which also reports how many emitted icons have no declared provenance at all.
Make CI fail when the committed font goes stale
Generated artifacts drift. Someone edits an SVG and does not regenerate; someone regenerates and does not commit. Without a check, the repository quietly holds a font built from an older pack.
name: CI
on: [push, pull_request]
jobs:
icons:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
- run: uv tool install glyphpact==1.0.1
# Exit code 3 means the committed artifacts are stale.
- run: glyphpact --config icon_font.json --check --json--check rebuilds a candidate set and compares it without rewriting the owned output. It may create the output's parent directory and leaves the sibling coordination file in place. With--json, inspect quality,policy, glyph counts, and typed issues rather than treating every zero exit as equivalent.
Large icon sets
One font holds at most 65,534 usable glyphs, an OpenType indexing limit GlyphPact enforces as a per-build ceiling. In practice the allocation range binds first: the default BMP private use area U+E000 - U+F8FF provides 6,400 lifetime slots, and tombstones consume them too because codepoints are never recycled.
For a larger catalogue, allocate from a supplementary private use area with --start-codepoint 0xF0000, which provides 65,534 slots. Beyond that, split into several independently versioned fonts.
Compile time depends far more on SVG complexity than on file count, so GlyphPact publishes no private-corpus headline number. Thebenchmark guide provides a reproducible local runner for measuring your own pack instead.
Reference
Go deeper
- Flutter adoption guideApp fonts, package fonts, layered icons, CI, and accessibility notes.
- Supported SVG profileThe versioned contract for exactly which SVG features compile.
- How codepoint stability worksWhy assignments move in other tools, and what the lock file does about it.
- Compared with FlutterIcon.comAn honest look at the hosted alternative, and where it wins.