Skip to content

Styling with utility classes

You style things by combining many single-purpose presentational classes (utility classes) directly in your Style widget:

Style('mx-auto flex max-w-sm items-center gap-x-4 rounded-xl bg-white p-6 shadow-lg outline outline-black/5 ...', children: [
Style('size-12 shrink-0', child: Image.network('/img/logo.svg')),
Style('...', children: [
Style('text-xl font-medium text-black ...', child: Text('ChitChat')),
Style('text-gray-500 ...', child: Text('You have a new message!')),
]),
])

In Tailwind CSS, you apply utilities like flex, rounded-xl, and shadow-lg directly in HTML class attributes. With the Style library, you write the exact same class strings in a Style widget:

Style('flex rounded-xl shadow-lg', child: ...)

Each utility class maps to a single CSS property — p-6 sets padding, bg-white sets a background color, rounded-xl sets border radius. By composing these small, single-purpose classes, you can build any design.


A common question is: “Isn’t this basically inline styles? Why not just use Flutter’s built-in properties?”

With utility classes you get:

  • Designing with constraints — Instead of picking arbitrary values like EdgeInsets.all(13), you choose from a pre-defined spacing scale (p-4, p-6, p-8) that produces more consistent designs.
  • Responsive design — Inline styles can’t adapt to breakpoints. Utility classes can: sm:flex-row changes layout direction only on small screens and above.
  • Hover, focus, and other states — Inline styles don’t handle pseudo-states. Utility classes do: hover:bg-sky-700 changes the background on hover.
class ProductEngineerPage extends StatelessWidget {
const ProductEngineerPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Style(
'mx-auto max-w-sm space-y-2 rounded-xl bg-white px-8 py-8 shadow-lg ring ring-black/5 sm:flex sm:items-center sm:space-y-0 sm:gap-x-6 sm:py-4',
children: [
Style(
'mx-auto block h-24 rounded-full sm:mx-0 sm:shrink-0',
child: Image.asset('assets/erin-lindford.jpg'),
),
Style(
'space-y-2 text-center sm:text-left',
children: [
Style(
'space-y-0.5',
children: [
Style(
'text-lg font-semibold text-black',
child: Text('Erin Lindford'),
),
Style(
'font-medium text-gray-500',
child: Text('Product Engineer'),
),
],
),
Style(
'inline-block rounded-full border border-purple-200 px-4 py-1 text-sm font-semibold text-purple-600 hover:border-transparent hover:bg-purple-600 hover:text-white active:bg-purple-700',
child: Text('Message'),
),
],
),
],
),
);
}
}

This component is fully responsive, includes hover and active states, and supports dark mode — all without writing a single BoxDecoration or MediaQuery by hand.


To style an element on hover or focus, prefix a utility class with the state name:

Style('bg-sky-500 hover:bg-sky-700 ...', child: Text('Save changes'))

The hover:bg-sky-700 class only applies when the widget is hovered. The Style widget manages interactive state tracking internally — no setState needed.

Learn more in Hover, focus, and other states.


Use responsive prefixes like sm:, md:, lg: to apply classes at specific breakpoints:

Style('grid grid-cols-2 gap-4 sm:grid-cols-3 ...', children: [
Text('01'),
// ...
Text('06'),
])

This grid starts with 2 columns on narrow screens and switches to 3 columns at the sm breakpoint (640px).

Learn more in Responsive design.


Prefix classes with dark: to apply them only when dark mode is active:

Light mode:

Style('bg-white rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5', children: [
Style('inline-flex items-center justify-center rounded-md bg-indigo-500 p-2 shadow-lg', child: Icon(Icons.edit, color: Colors.white, size: 24)),
Style('text-gray-900 mt-5 text-base font-medium tracking-tight', child: Text('Writes upside-down')),
Style('text-gray-500 mt-2 text-sm', child: Text('The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.')),
])

Dark mode:

The same component with ThemeMode.darkdark:bg-gray-800 replaces the white background, and dark:text-white replaces the dark text.

The dark:bg-gray-800 and dark:text-white classes activate when the app’s ThemeMode is set to dark.

Learn more in Dark mode.


You can combine multiple utility classes freely. They each set independent properties, so they compose naturally:

Style('blur-xs', child: Image.network('/img/mountains.jpg'))
Style('grayscale', child: Image.network('/img/mountains.jpg'))
Style('blur-xs grayscale', child: Image.network('/img/mountains.jpg'))

Here blur-sm and grayscale each apply a separate image filter, and they stack without conflict.


When the design requires a value outside the default theme scale, use square bracket notation:

Style('bg-[#316ff6] ...', child: Text('Sign in with Facebook'))

The bg-[#316ff6] class sets an exact hex color. You can use arbitrary values for any property — colors, sizes, spacing, and more.


The group class marks a parent element so descendants can respond to its hover state using group-hover::

Style('group rounded-lg p-8 ...', children: [
// ...
Style('group-hover:underline', child: Text('Read more...')),
])

When you hover anywhere on the parent Style (which has the group class), the “Read more…” text becomes underlined.


When rendering a list of similar elements, use a Dart for loop or .map() method:

Style('flex items-center space-x-2 text-base', children: [
Text('Contributors'),
Style('rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700', child: Text('204')),
])
Style('mt-3 flex -space-x-2 overflow-hidden', children: [
for (final url in avatars)
Style('inline-block h-12 w-12 rounded-full ring-2 ring-white', child: Image.network(url)),
])
Style('mt-3 text-sm font-medium', child: Style('text-blue-500', child: Text('+ 198 others')))

The -space-x-2 class creates the overlapping effect, and ring-2 ring-white adds a white border around each avatar.

For complex patterns that repeat across your app, extract a reusable widget — just like you would in any Flutter project:

class VacationCard extends StatelessWidget {
const VacationCard({required this.imageUrl, required this.eyebrow, required this.title, required this.pricing});
final String imageUrl;
final Widget eyebrow;
final Widget title;
final Widget pricing;
@override
Widget build(BuildContext context) {
return Style('flex flex-col items-start', children: [
Style('rounded-lg overflow-hidden', child: Image.network(imageUrl)),
Style('mt-4', children: [
Style('text-xs font-bold text-sky-500 ...', child: eyebrow),
Style('mt-1 font-bold text-gray-700 hover:underline', child: title),
Style('mt-2 text-sm text-gray-600', child: pricing),
]),
]);
}
}

The utility classes stay in the widget tree, but the component is reusable with different data. This is the Flutter equivalent of extracting a component in React or Vue.