Skip to content

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.


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.


All fields are optional. A null field means “use the Tailwind default.”

FieldTypeDescription
colorsMap<String, Map<int, Color>>Color palette with shades (50-950)
standaloneColorsMap<String, Color>Shade-less colors (e.g., brand without a number)
spacingMap<String, double>Spacing scale in logical pixels
typographyMap<String, ({double fontSize, double lineHeight})>Typography scale
letterSpacingMap<String, double>Letter-spacing (tracking) scale
leadingMap<String, double>Line-height (leading) scale
radiusMap<String, double>Border-radius scale
shadowsMap<String, List<BoxShadowData>>Box-shadow presets
maxWidthsMap<String, double>Named max-width values
breakpointsList<({String name, double width})>Custom responsive breakpoints
customPropertiesMap<String, String>CSS custom properties backing the utility-(--var) syntax

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.


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.


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.

StyleTheme.of(context) checks in this order:

  1. Nearest StyleTheme ancestor widget
  2. StyleThemeExtension from ThemeData.extensions
  3. StyleThemeData.defaultTheme (all Tailwind defaults)

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.


StyleThemeData(
colors: {
'brand': {
50: Color(0xFFF0F9FF),
500: Color(0xFF0EA5E9),
900: Color(0xFF0C4A6E),
},
'accent': {
500: Color(0xFFF59E0B),
},
},
)
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
},
)
StyleThemeData(
typography: {
'sm': (fontSize: 13, lineHeight: 20),
'base': (fontSize: 15, lineHeight: 24),
},
)
StyleThemeData(
radius: {
'lg': 12, // Override rounded-lg from 8px to 12px
'xl': 20, // Override rounded-xl from 12px to 20px
},
)
StyleThemeData(
breakpoints: [
(name: 'tablet', width: 600),
(name: 'desktop', width: 1000),
(name: 'wide', width: 1400),
],
)
StyleThemeData(
customProperties: {
'--brand-padding': '1.5rem', // used as p-(--brand-padding)
'--brand': '#316ff6', // used as bg-(--brand)
},
)