Skip to content

Hover, focus, and other states

Every utility class in Style can be applied conditionally by adding a variant prefix that targets a specific state. Variants are separated from the utility by a colon — for example, hover:bg-sky-700 only applies bg-sky-700 when the element is hovered.

Style('bg-violet-500 hover:bg-violet-600 focus:outline-2 focus:outline-offset-2 focus:outline-violet-500 active:bg-violet-700 ...', child: Text('Save changes'))

The Style widget manages hover, focus, and active state tracking internally — you don’t need setState, MouseRegion, or GestureDetector.


Style these states with the hover:, focus:, and active: prefixes:

VariantApplies when
hover:Mouse pointer is over the widget
focus:Widget has keyboard focus
active:Widget is being pressed
// Background changes on hover
Style('bg-sky-500 hover:bg-sky-700', child: ...)
// Outline appears on focus
Style('focus:outline-2 focus:outline-sky-500', child: ...)
// Darker shade while pressed
Style('bg-violet-500 active:bg-violet-700', child: ...)

You can combine all three on a single widget — they compose naturally:

Style(
'bg-violet-500 hover:bg-violet-600 focus:outline-2 focus:outline-offset-2 focus:outline-violet-500 active:bg-violet-700 cursor-pointer',
child: ...,
)

Use the disabled: prefix for styling disabled elements. Pass disabled: true to the Style widget:

Style('bg-indigo-500 hover:bg-indigo-600 ...', child: Text('Enabled'))
Style('bg-indigo-500 disabled:bg-gray-400 disabled:cursor-not-allowed ...', disabled: true, child: Text('Disabled'))

When disabled: true is set, disabled: prefixed classes activate and hover/active states are suppressed.


A common pattern is showing a visible outline when an element receives keyboard focus:

Style('focus-within:border-sky-500 focus-within:outline focus-within:outline-sky-500 ...', child: TextField())

The focus:border-sky-500 focus:outline focus:outline-sky-500 classes apply a blue border and outline ring when the element is focused.


Use the group class on a parent and group-hover: on children to style children based on the parent’s hover state:

Style('group ...', children: [
Style('stroke-sky-500 group-hover:stroke-white ...', children: [
// ...
]),
Style('text-gray-900 group-hover:text-white ...', child: Text('New project')),
Style('text-gray-500 group-hover:text-white ...', child: Text('Create a new project from a variety of starting templates.')),
])

When you hover anywhere on the card (the group), the background turns sky-500, and the text turns white via group-hover:text-white.

Other group variants available:

  • group-focus: — when the group has focus
  • group-active: — when the group is pressed
  • group-focus-within: — when the group or any descendant has focus

Use the peer class on a sibling and peer-hover:, peer-focus:, or peer-active: on other siblings to style based on the peer’s state:

Style('flex flex-col gap-2 max-w-sm p-8', children: [
Text('Email'),
Style('peer ...', child: TextField()),
Style('invisible peer-focus:visible ...', child: Text('Enter your email address.')),
])

The helper text is invisible by default and becomes visible via peer-focus:visible when the peer element (the input) receives focus.

Other peer variants available:

  • peer-hover: — when the peer is hovered
  • peer-focus: — when the peer has focus
  • peer-active: — when the peer is pressed
  • peer-focus-within: — when the peer or its descendants have focus

Use the dark: prefix to style elements differently in dark mode:

Style(
'bg-white dark:bg-gray-800 text-gray-900 dark:text-white',
child: ...,
)

Dark mode classes activate based on the app’s ThemeMode. See Dark mode for more details.


Breakpoint prefixes can be combined with state variants:

// On small screens and above, change background on hover
Style('bg-white sm:hover:bg-sky-100', child: ...)

Variants can be stacked — sm:hover:bg-sky-100 means “at the sm breakpoint and above, when hovered, apply bg-sky-100.”

See Responsive design for the full breakpoint system.


Structural variants style a flex, grid, or block child based on its position among its siblings — mirroring the CSS structural pseudo-classes:

VariantApplies when the child is…
first:the first child (:first-child)
last:the last child (:last-child)
only:the only child (:only-child)
odd:at an odd position (:nth-child(odd))
even:at an even position (:nth-child(even))
nth-3:the 3rd child (:nth-child(3))
nth-[2n+1]:matches the An+B formula (:nth-child(2n+1))
Style('flex', children: [
for (final item in items)
// first item gets extra left padding, last gets extra right padding
Style('px-2 first:pl-6 last:pr-6', child: item),
])

The position is resolved against the direct children of the nearest Style layout container (flex / grid / block). It works whether a child is a Style directly or a custom StatelessWidget / StatefulWidget component that returns one — so you can factor a list item into its own widget and first: / last: still apply. Like CSS :nth-child, every sibling counts, including absolute-positioned ones.

You can stack multiple variants on a single utility:

Style('dark:hover:bg-indigo-600', child: ...)

This applies bg-indigo-600 only when both dark mode is active and the element is hovered.

Common combinations:

  • dark:hover: — dark mode + hover
  • sm:hover: — breakpoint + hover
  • group-hover: — parent hover
  • focus:active: — focused and pressed

VariantDescription
hover:Mouse over the widget
focus:Widget has keyboard focus
focus-within:Widget or descendant has focus
active:Widget is being pressed
disabled:Widget has disabled: true
dark:Dark mode is active
group-hover:Parent with group class is hovered
group-focus:Parent with group class is focused
group-active:Parent with group class is pressed
peer-hover:Sibling with peer class is hovered
peer-focus:Sibling with peer class is focused
peer-active:Sibling with peer class is pressed

Structural variants (first:, last:, only:, odd:, even:, nth-*:) are also supported — see Structural variants above.