Custom themes
The StyleTheme widget lets you override any design token — colors, spacing, typography, border radius, shadows, and more — for an entire subtree of Style widgets.
Basic usage
Section titled “Basic usage”Wrap any widget tree with StyleTheme to provide custom tokens:
class BrandColorExample extends StatelessWidget { const BrandColorExample({super.key});
@override Widget build(BuildContext context) { return StyleTheme( data: StyleThemeData( colors: { 'brand': { 50: Color(0xFFEFF6FF), 100: Color(0xFFDBEAFE), 500: Color(0xFF316FF6), 700: Color(0xFF1D4ED8), 900: Color(0xFF1E3A8A), }, }, ), child: Style( 'bg-brand-500 hover:bg-brand-700 text-white font-bold py-3 px-6 rounded-lg shadow-lg', child: Text('Brand Button'), ), ); }}Custom colors become available as utility classes immediately — bg-brand-500, text-brand-700, border-brand-100, etc.
StyleThemeData API
Section titled “StyleThemeData API”All fields are optional. A null field means “use the Tailwind default.”
| Field | Type | Description |
|---|---|---|
colors | Map<String, Map<int, Color>> | Color palette with shades (50-950) |
standaloneColors | Map<String, Color> | Shade-less colors (e.g., brand without a number) |
spacing | Map<String, double> | Spacing scale in logical pixels |
typography | Map<String, ({double fontSize, double lineHeight})> | Typography scale |
letterSpacing | Map<String, double> | Letter-spacing (tracking) scale |
leading | Map<String, double> | Line-height (leading) scale |
radius | Map<String, double> | Border-radius scale |
shadows | Map<String, List<BoxShadowData>> | Box-shadow presets |
maxWidths | Map<String, double> | Named max-width values |
breakpoints | List<({String name, double width})> | Custom responsive breakpoints |
customProperties | Map<String, String> | CSS custom properties backing the utility-(--var) syntax |
Overriding spacing
Section titled “Overriding spacing”Override specific spacing tokens while keeping the rest of the Tailwind defaults:
class SpacingExample extends StatelessWidget { const SpacingExample({super.key});
@override Widget build(BuildContext context) { return StyleTheme( data: StyleThemeData(spacing: {'4': 24, '8': 48}), child: Style( 'flex flex-wrap gap-4 items-center', children: [ Style( 'bg-sky-500 p-4 rounded-lg text-white', child: Text('p-4 = 24px'), ), Style( 'bg-sky-500 p-8 rounded-lg text-white', child: Text('p-8 = 48px'), ), Style( 'bg-sky-500 p-3 rounded-lg text-white', child: Text('p-3 = 12px (default)'), ), ], ), ); }}Only the overridden tokens change — all other spacing values (p-1, p-2, p-3, p-6, etc.) remain at their Tailwind defaults.
Nested themes with merge
Section titled “Nested themes with merge”Use StyleTheme.merge directly above a single Style to override one token for just that Style — its siblings still see the outer theme:
class NestedExample extends StatelessWidget { const NestedExample({super.key});
@override Widget build(BuildContext context) { return StyleTheme( data: StyleThemeData( colors: { 'brand': {500: Color(0xFF316FF6)}, }, spacing: {'4': 20}, ), child: Style( 'flex flex-col gap-4 items-center', children: [ Style( 'bg-brand-500 p-4 rounded-lg text-white', child: Text('Inherited: brand-500 + p-4 = 20px'), ), StyleTheme.merge( data: StyleThemeData(spacing: {'4': 32}), child: Style( 'bg-brand-500 p-4 rounded-lg text-white', child: Text('Merged spacing: p-4 = 32px'), ), ), StyleTheme.merge( data: StyleThemeData( colors: { 'brand': {500: Color(0xFFE11D48)}, }, ), child: Style( 'bg-brand-500 p-4 rounded-lg text-white', child: Text('Merged color: brand-500 = red'), ), ), ], ), ); }}All three buttons use the same class string bg-brand-500 p-4 rounded-lg text-white. The first one inherits both brand-500 and spacing['4'] from the outer theme — like any Flutter InheritedWidget, descendants see the same values until something explicitly overrides them. Each StyleTheme.merge wraps a single Style and merges its data with the outer theme at the map-entry level: only the listed token is replaced, everything else still inherits.
Material ThemeExtension
Section titled “Material ThemeExtension”Provide theme data through Material’s ThemeData.extensions instead of a wrapping widget:
class ExtensionExample extends StatelessWidget { const ExtensionExample({super.key});
@override Widget build(BuildContext context) { return StyleTheme( data: StyleThemeData( colors: { 'primary': {500: Color(0xFF6200EE)}, }, ), child: Style( 'bg-primary-500 text-white font-bold py-3 px-6 rounded-lg', child: Text('Via ThemeExtension'), ), ); }}This is useful when you want to configure Style tokens at the app level alongside your Material theme. The preview above shows the visual result — in your app, the StyleThemeExtension in ThemeData.extensions provides the same tokens without needing a StyleTheme widget wrapper.
Lookup order
Section titled “Lookup order”StyleTheme.of(context) checks in this order:
- Nearest
StyleThemeancestor widget StyleThemeExtensionfromThemeData.extensionsStyleThemeData.defaultTheme(all Tailwind defaults)
Custom properties
Section titled “Custom properties”Tailwind v4’s utility-(--var) syntax is shorthand for utility-[var(--var)] — a utility resolved against a CSS custom property instead of a literal value. customProperties backs the theme-defined subset of this syntax: keys are property names WITH the leading -- (matching how you’d write them in a Tailwind @theme block), values are the same value strings you’d write inside [...] brackets.
StyleTheme( data: StyleThemeData( customProperties: {'--brand-padding': '1.5rem', '--brand': '#316ff6'}, ), child: Style( 'p-(--brand-padding) bg-(--brand)', // same as p-[1.5rem] bg-[#316ff6] child: Text('Themed'), ),)Because substitution happens before arbitrary-value parsing, every utility that already accepts [...] gains (--var) for free — no per-utility support is needed. The typed-hint form (p-(length:--brand-padding), bg-(color:--brand)) is accepted and the hint is ignored, since substitution already yields a concrete value. A reference to an undefined property resolves the utility to nothing — the same as an invalid arbitrary value — rather than throwing. Nested themes merge customProperties like every other map: StyleTheme.merge overrides only the keys it defines, leaving the rest inherited.
Cascade-inherited or JS-set CSS variables have no Flutter runtime equivalent and remain out of scope — only theme-defined properties are resolved.
Override examples
Section titled “Override examples”Custom colors
Section titled “Custom colors”StyleThemeData( colors: { 'brand': { 50: Color(0xFFF0F9FF), 500: Color(0xFF0EA5E9), 900: Color(0xFF0C4A6E), }, 'accent': { 500: Color(0xFFF59E0B), }, },)Custom spacing
Section titled “Custom spacing”StyleThemeData( spacing: { '1': 6, // Override p-1 from 4px to 6px '4': 20, // Override p-4 from 16px to 20px 'xs': 4, // Add a new token: p-xs },)Custom typography
Section titled “Custom typography”StyleThemeData( typography: { 'sm': (fontSize: 13, lineHeight: 20), 'base': (fontSize: 15, lineHeight: 24), },)Custom border radius
Section titled “Custom border radius”StyleThemeData( radius: { 'lg': 12, // Override rounded-lg from 8px to 12px 'xl': 20, // Override rounded-xl from 12px to 20px },)Custom breakpoints
Section titled “Custom breakpoints”StyleThemeData( breakpoints: [ (name: 'tablet', width: 600), (name: 'desktop', width: 1000), (name: 'wide', width: 1400), ],)Custom properties
Section titled “Custom properties”StyleThemeData( customProperties: { '--brand-padding': '1.5rem', // used as p-(--brand-padding) '--brand': '#316ff6', // used as bg-(--brand) },)