Skip to content

Widget States

Kenny Mochizuki Escalona edited this page Aug 13, 2026 · 1 revision

Widget States

layrz_ui re-exports Flutter's interactive state management types from package:flutter/widgets.dart. These are deliberately not wrapped or reimplemented — they are exported verbatim because they are design-system-agnostic and suitable for use in any widget system.


Overview

Interactive components (buttons, inputs, chips, and other controls) resolve visual properties (color, opacity, size) based on their current interactive state (hover, press, focus, disabled, etc.). Flutter provides two abstractions for this:

  • WidgetState — an enum representing interactive conditions
  • WidgetStateProperty<T> — an interface for values that vary based on widget state

layrz_ui exports these types so widgets can use them consistently.


What is Re-exported

Type Purpose
WidgetState Enum of interactive states (hovered, pressed, focused, disabled, etc.)
WidgetStateProperty<T> Interface for state-aware property values
WidgetStateMapper<T> Function type for mapping states to values
WidgetStatePropertyAll<T> Concrete property — single value for all states
WidgetStatesController Mutable ValueNotifier tracking active states
WidgetStatesConstraint Constraint limiting which states can be active simultaneously
WidgetStateMap<T> Concrete property — map of states to values
WidgetPropertyResolver<T> Function type for state-to-value resolution
WidgetStateColor Convenience property for Color values
WidgetStateTextStyle Convenience property for TextStyle values
WidgetStateBorderSide Convenience property for BorderSide values
WidgetStateMouseCursor Convenience property for MouseCursor values
WidgetStateOutlinedBorder Convenience property for OutlinedBorder values

Import them directly from layrz_ui:

import 'package:layrz_ui/layrz_ui.dart';

// Available as:
WidgetState.pressed
WidgetStateProperty<Color>
WidgetStateColor

WidgetState: Interactive Conditions

An enum representing the possible interactive states a widget can be in:

State Meaning
hovered User is hovering over the widget (desktop platforms)
focused Widget has keyboard focus
pressed User is actively pressing the widget
dragged Widget is being dragged (for draggable widgets)
error Widget is in an error state
disabled Widget is disabled and does not respond to input
selected Widget is selected (for selectable widgets like checkboxes)

WidgetStateProperty: State-Aware Values

WidgetStateProperty<T> is a functional interface that resolves a value of type T based on a set of active WidgetStates:

abstract class WidgetStateProperty<T> {
  T resolve(Set<WidgetState> states);
}

Concrete Implementations

WidgetStatePropertyAll

A single value for all states:

WidgetStatePropertyAll<Color>(Colors.blue)
// Always returns Colors.blue, regardless of state

WidgetStateMap

A map of states to values:

WidgetStateMap<Color>({
  WidgetState.hovered: Colors.lightBlue,
  WidgetState.pressed: Colors.darkBlue,
  WidgetState.disabled: Colors.grey,
})
// Returns different color based on which states are active

WidgetStateColor, WidgetStateTextStyle, etc.

Convenience implementations for common types:

WidgetStateColor({
  WidgetState.hovered: Colors.lightBlue,
  WidgetState.pressed: Colors.darkBlue,
  WidgetState.disabled: Colors.grey,
})

WidgetStatesController: Mutable State Tracking

A ValueNotifier that tracks the set of active states and notifies listeners when they change:

final controller = WidgetStatesController();

// Activate states
controller.update(WidgetState.hovered, true);
controller.update(WidgetState.pressed, true);

// Check active states
if (controller.value.contains(WidgetState.pressed)) {
  // Handle pressed state
}

// Listen for changes
controller.addListener(() {
  print('States changed: ${controller.value}');
});

Practical Example: Custom Button

A simplified example of how a custom button might use WidgetStateProperty:

class CustomButton extends StatefulWidget {
  const CustomButton({
    required this.onTap,
    required this.label,
    this.backgroundColor = const WidgetStatePropertyAll(Colors.blue),
    super.key,
  });

  final VoidCallback onTap;
  final String label;
  final WidgetStateProperty<Color> backgroundColor;

  @override
  State<CustomButton> createState() => _CustomButtonState();
}

class _CustomButtonState extends State<CustomButton> {
  final _statesController = WidgetStatesController();

  @override
  Widget build(BuildContext context) {
    return MouseRegion(
      onEnter: (_) => _statesController.update(WidgetState.hovered, true),
      onExit: (_) => _statesController.update(WidgetState.hovered, false),
      child: GestureDetector(
        onTapDown: (_) => _statesController.update(WidgetState.pressed, true),
        onTapUp: (_) => _statesController.update(WidgetState.pressed, false),
        onTapCancel: () => _statesController.update(WidgetState.pressed, false),
        onTap: widget.onTap,
        child: ValueListenableBuilder<Set<WidgetState>>(
          valueListenable: _statesController,
          builder: (context, states, _) {
            final backgroundColor = widget.backgroundColor.resolve(states);
            return Container(
              decoration: BoxDecoration(
                color: backgroundColor,
                borderRadius: BorderRadius.circular(8),
              ),
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              child: Text(widget.label),
            );
          },
        ),
      ),
    );
  }

  @override
  void dispose() {
    _statesController.dispose();
    super.dispose();
  }
}

Usage:

CustomButton(
  onTap: () => print('Tapped'),
  label: 'Click me',
  backgroundColor: WidgetStateProperty.resolveWith((states) {
    if (states.contains(WidgetState.pressed)) {
      return Colors.darkBlue;
    }
    if (states.contains(WidgetState.hovered)) {
      return Colors.lightBlue;
    }
    return Colors.blue;
  }),
)

Decision D13: Interactive State Re-export

layrz_ui deliberately re-exports these types rather than wrapping them because:

  1. Design-system agnostic — These types are not specific to any design system; they belong in Flutter core
  2. No value added by wrapping — Any wrapper would be a thin pass-through; wrapping adds complexity without benefit
  3. Adoption — Components can adopt the standard Flutter patterns without learning layrz_ui-specific abstractions

See decision D13 for full details.


Flutter Documentation

For complete API reference and more examples, refer to the Flutter documentation:


Last updated: 2026-08-13
Related pages: Getting-Started, LayrzApp

Clone this wiki locally