fabrik_theme
Flutter’s ThemeData gives you colorScheme.primary and textTheme.bodyLarge.
That covers Material’s own widgets, but says nothing about your app’s roles —
the color of a helper label, the style of an emphasised value in a list row.
fabrik_theme adds a semantic layer on top: you define colors and text styles
once, by meaning, and read them anywhere through context.
Text('Payment failed', style: TextStyle(color: context.colors.error))Text('Last updated', style: context.typography.bodySmallSecondary)Installation
Section titled “Installation”dependencies: fabrik_theme: ^1.1.0import 'package:fabrik_theme/fabrik_theme.dart';Quick start
Section titled “Quick start”Build both themes from the bundled palettes and you have a working light/dark app in one step:
MaterialApp( theme: FabrikTheme.create( brightness: Brightness.light, colors: AppColors.defaults(), ), darkTheme: FabrikTheme.create( brightness: Brightness.dark, colors: AppColors.darkDefaults(), ), home: const HomePage(),);Then read colors and typography anywhere below:
Container( color: context.colors.surface, child: Text('Welcome', style: context.typography.headlineMedium),);Typography is generated from your colors automatically — you only supply an
AppTypography if you want to override it.
Guides
Section titled “Guides”Define your own palette
Section titled “Define your own palette”AppColors is a contract of semantic roles. Define one palette per brightness:
class AppPalette { static const light = AppColors( primary: Color(0xFF6C5CE7), onPrimary: Color(0xFFFFFFFF), accent: Color(0xFF00CEC9), onAccent: Color(0xFFFFFFFF), surface: Color(0xFFFFFFFF), onSurface: Color(0xFF111111), textPrimary: Color(0xFF111111), textSecondary: Color(0xFF444444), textTertiary: Color(0xFF777777), error: Color(0xFFB3261E), onError: Color(0xFFFFFFFF), );
static const dark = AppColors( primary: Color(0xFFB4A7FF), onPrimary: Color(0xFF1A1A2E), accent: Color(0xFF55EFC4), onAccent: Color(0xFF003D33), surface: Color(0xFF121212), onSurface: Color(0xFFFFFFFF), textPrimary: Color(0xFFFFFFFF), textSecondary: Color(0xFFCCCCCC), textTertiary: Color(0xFF999999), error: Color(0xFFF2B8B5), onError: Color(0xFF601410), );}The on* roles are the foreground used on top of their counterpart. If
primary is your button fill, onPrimary is the label on that button.
Get dark mode right
Section titled “Get dark mode right”A dark theme needs a dark palette. Passing a light palette with
Brightness.dark darkens Material’s own ColorScheme, but your semantic
colors stay light — and the result is black text on a white surface with no
error to tell you why.
// Wrong: dark brightness, light paletteFabrikTheme.create( brightness: Brightness.dark, colors: AppPalette.light, // context.colors.surface is still white);
// RightFabrikTheme.create( brightness: Brightness.dark, colors: AppPalette.dark,);Style failure states
Section titled “Style failure states”error and onError are part of the palette, so failures stay inside the
design system instead of reaching for Colors.red:
Text( 'Card was declined', style: context.typography.bodyMedium.copyWith( color: context.colors.error, ),);These roles are also fed into Material’s ColorScheme, so TextField error
borders and your own error text use the same color.
Both are optional in the constructor and fall back to ColorTokens.error and
ColorTokens.onError, so palettes written before this release keep working.
Apply a custom font
Section titled “Apply a custom font”Pass fontFamily and it is applied across every generated text style:
FabrikTheme.create( brightness: Brightness.light, colors: AppPalette.light, fontFamily: 'Inter',);If you pass your own AppTypography, fontFamily is ignored — your styles are
used exactly as written.
Choose the right text style
Section titled “Choose the right text style”AppTypography exposes semantic variants rather than one style per size. The
naming follows a consistent pattern:
| Suffix | Meaning | Example |
|---|---|---|
| (none) | Default text color | bodyLarge |
Primary | Brand-colored, for accents | headlineLargePrimary |
Secondary | Lower emphasis | bodyMediumSecondary |
Tertiary | Lowest emphasis, helper text | bodySmallTertiary |
Emphasis | Same color, heavier weight | bodyLargeEmphasis |
Text('Order #1042', style: context.typography.titleMedium)Text('Delivered', style: context.typography.bodyMediumSecondary)Text('48.00', style: context.typography.bodyLargeEmphasis)API reference
Section titled “API reference”FabrikTheme.create
Section titled “FabrikTheme.create”Builds a complete ThemeData with both extensions registered.
| Parameter | Type | Default | Description |
|---|---|---|---|
brightness | Brightness | required | Light or dark |
colors | AppColors | required | Your semantic palette |
typography | AppTypography? | derived | Overrides the generated typography |
fontFamily | String? | null | Applied to generated typography only |
AppColors
Section titled “AppColors”| Role | Purpose |
|---|---|
primary / onPrimary | Key actions and emphasis |
accent / onAccent | Secondary emphasis |
surface / onSurface | Backgrounds and content on them |
textPrimary | High-emphasis text |
textSecondary | Supporting text |
textTertiary | Helper and placeholder text |
error / onError | Failures and destructive actions |
Factories: AppColors.defaults() (light), AppColors.darkDefaults() (dark).
Both implement copyWith, lerp, == and hashCode.
Design tokens
Section titled “Design tokens”Raw values used to compose semantic roles. Access them directly for layout
work — they do not require a BuildContext.
| Token class | Scale |
|---|---|
SpacingTokens | none xxs xs sm md lg xl xxl xxxl (0–48) |
RadiusTokens | none xs sm md lg xl full (0–999) |
ElevationTokens | none xs sm md lg xl (0–24) |
BorderTokens | none thin medium thick (0–2) |
ColorTokens | Base palette, light and dark variants |
TypographyTokens | Font sizes, line heights, letter spacing |
Padding( padding: const EdgeInsets.all(SpacingTokens.lg), child: Card( elevation: ElevationTokens.sm, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(RadiusTokens.md), ), child: content, ),);Context extensions
Section titled “Context extensions”| Getter | Returns | Throws |
|---|---|---|
context.colors | AppColors | StateError if the theme was not built with FabrikTheme.create |
context.typography | AppTypography | StateError, as above |
Both throw in release builds as well as debug, so a missing theme fails with a readable message rather than a null dereference.