Skip to content

Custom variants

StyleVariants lets you define custom variant predicates that respond to any BuildContext condition — screen size, locale, directionality, inherited state, or anything else.


Wrap a widget tree with StyleVariants to register custom variant names:

class CustomVariantsPage extends StatelessWidget {
const CustomVariantsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: StyleVariants(
variants: {
'compact': (context) => MediaQuery.sizeOf(context).width < 500,
},
child: Style(
'flex flex-col gap-4 p-8 items-center',
children: [
Style(
'text-sm text-gray-500',
child: Text('Resize browser to < 500px for compact mode'),
),
Style(
'bg-indigo-500 text-white font-bold py-4 px-8 rounded-lg compact:py-2 compact:px-4 compact:text-sm',
child: Text('Adaptive Button'),
),
],
),
),
);
}
}

Custom variants use the same prefix: syntax as built-in variants like hover: and dark:. When the predicate returns true, the prefixed classes are applied.


Override Tailwind’s default breakpoints with StyleTheme:

class CustomBreakpointsPage extends StatelessWidget {
const CustomBreakpointsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: StyleTheme(
data: StyleThemeData(
breakpoints: [
(name: 'tablet', width: 600),
(name: 'desktop', width: 1000),
],
),
child: Style(
'flex flex-col gap-4 p-8 items-center',
children: [
Style(
'text-sm text-gray-500',
child: Text('Resize to see breakpoints'),
),
Style(
'bg-emerald-500 text-white font-bold p-4 rounded-lg tablet:p-6 desktop:p-8 tablet:text-lg desktop:text-xl',
child: Text('Responsive with custom breakpoints'),
),
],
),
),
);
}
}

Custom breakpoint names become available as responsive prefixes: tablet:, desktop:, etc.


Use StyleVariants.merge to add variants while preserving those from ancestor widgets:

StyleVariants(
variants: {
'compact': (context) => MediaQuery.sizeOf(context).width < 500,
},
child: StyleVariants.merge(
variants: {
'rtl': (context) => Directionality.of(context) == TextDirection.rtl,
},
child: Style(
'p-4 compact:p-2 rtl:text-right',
child: Text('Both variants available'),
),
),
)

Child variants override parent variants with the same name.


Custom variants can respond to any context condition:

StyleVariants(
variants: {
// Screen-size based
'compact': (ctx) => MediaQuery.sizeOf(ctx).width < 500,
'tall': (ctx) => MediaQuery.sizeOf(ctx).height > 900,
// Locale/direction based
'rtl': (ctx) => Directionality.of(ctx) == TextDirection.rtl,
// Platform based
'ios': (ctx) => Theme.of(ctx).platform == TargetPlatform.iOS,
'android': (ctx) => Theme.of(ctx).platform == TargetPlatform.android,
// Accessibility
'high-contrast': (ctx) => MediaQuery.highContrastOf(ctx),
'reduce-motion': (ctx) => MediaQuery.disableAnimationsOf(ctx),
},
child: myApp,
)

Variants and themes are independent and compose naturally:

StyleTheme(
data: StyleThemeData(
colors: {'brand': {500: Color(0xFF316FF6)}},
),
child: StyleVariants(
variants: {
'compact': (ctx) => MediaQuery.sizeOf(ctx).width < 500,
},
child: Style(
'bg-brand-500 text-white p-6 compact:p-3 rounded-lg',
child: Text('Themed + responsive'),
),
),
)

Custom colors from StyleTheme and custom variants from StyleVariants both apply to the same Style widget.