flutterdartmaterial-ui

Text style will not update when state contains WidgetState.selected


I'm using OutlinedButtons as options in a form that can be selected. When an OutlinedButton is pressed, I want to make the stroke thicker, the text bolder, and add a small circular indicator.

I'm able to make the stroke thicker and add the indicator, but I'm having a really hard time making the text bolder. Here is my most recent code:

/***
The OutlinedButton
***/
class OutlinedButtonChoice extends StatefulWidget {
  final bool isSelected;
  final void Function() onPressed;
  final String label;

  const OutlinedButtonChoice({
    super.key,
    required this.isSelected,
    required this.onPressed,
    required this.label,
  });

  @override
  State<OutlinedButtonChoice> createState() => _OutlinedButtonChoiceState();
}

class _OutlinedButtonChoiceState extends State<OutlinedButtonChoice> {
  late WidgetStatesController _statesController;

  @override
  void initState() {
    super.initState();
    _statesController = WidgetStatesController(<WidgetState>{
      if (widget.isSelected) WidgetState.selected,
    });
  }

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

  @override
  void didUpdateWidget(OutlinedButtonChoice oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isSelected != oldWidget.isSelected) {
      _statesController.update(WidgetState.selected, widget.isSelected);
    }
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    final baseStyle =
        theme.outlinedButtonTheme.style ?? OutlinedButton.styleFrom();

    final realStyle = baseStyle.copyWith(
      minimumSize: const WidgetStatePropertyAll(Size(0, 50)),
    );

    return OutlinedButton(
      onPressed: widget.onPressed,
      style: realStyle,
      statesController: _statesController,
      child: Row(
        spacing: 5.0,
        children: [
          Text(widget.label),
          if (widget.isSelected)
            Container(
              width: 8,
              height: 8,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: theme.primaryColor,
              ),
            ),
        ],
      ),
    );
  }
}

/***
Truncated custom ThemeData
***/
final bodyLarge = GoogleFonts.roboto(fontSize: 22, fontWeight: FontWeight.w300);

final myThemeData = ThemeData(
  outlinedButtonTheme: OutlinedButtonThemeData(
    style: ButtonStyle(
      foregroundColor: WidgetStatePropertyAll(_brandColor),
      shape: WidgetStatePropertyAll(
        RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
      ),
      side: WidgetStateProperty.resolveWith<BorderSide>((states) {
        final width = states.contains(WidgetState.selected) ? 2.5 : 1.0;
        return BorderSide(width: width, color: _brandColor);
      }),
      textStyle: WidgetStateProperty.resolveWith<TextStyle?>((states) {
        if (states.contains(WidgetState.selected)) {
          return textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w800);
        }
        return textTheme.bodyLarge;
      }),
    ),
  ),
);

Please assume that I'm passing isSelected correctly to the OutlinedButtonChoice. Other information:

Flutter 3.32.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 6fba2447e9 (5 days ago) • 2025-06-12 19:03:56 -0700
Engine • revision 8cd19e509d (6 days ago) • 2025-06-12 16:30:12 -0700
Tools • Dart 3.8.1 • DevTools 2.45.1

Platform: MacOS

The only way I've been able to get this to work is to assign a style to the Text child of the OutlinedButton, but that feels rather brittle. If this isn't possible, is there perhaps another widget I can use? Thank you!


Solution

  • you are right but there is a catch , when a Text widget is passed as a child to a button, any direct style: applied on that Text will override what the button's textStyle attempts to provide.

    like in your case

    Text(widget.label)
    

    Wrap your label + indicator in a Builder, and manually apply the text style from the current button Theme. Here’s how you can do it properly:

    @override
    Widget build(BuildContext context) {
      final theme = Theme.of(context);
      final buttonStyle = theme.outlinedButtonTheme.style ?? OutlinedButton.styleFrom();
      final effectiveTextStyle = buttonStyle.textStyle?.resolve(_statesController.value) ??
          DefaultTextStyle.of(context).style;
    
      return OutlinedButton(
        onPressed: widget.onPressed,
        style: buttonStyle.copyWith(
          minimumSize: const WidgetStatePropertyAll(Size(0, 50)),
        ),
        statesController: _statesController,
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              widget.label,
              style: effectiveTextStyle, // manually apply the resolved style
            ),
            if (widget.isSelected) ...[
              const SizedBox(width: 8),
              Container(
                width: 8,
                height: 8,
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: theme.primaryColor,
                ),
              ),
            ],
          ],
        ),
      );
    }