RemixRemix
Actions and inputs

Toggle Group

A single-select segmented control with roving keyboard focus

Open catalog
Starting Flutter example…
Live Flutter from this checkout’s component catalog. Examples include Fortal styling; the source tab shows the catalog code, not a standalone application.

A toggle group presents a compact set of mutually exclusive options. Only one item is in the Tab order; arrow keys move focus without changing selection.

When to use this

  • View switching: Choose list, grid, or board presentation
  • Formatting choices: Pick one alignment or text mode
  • Compact filters: Select one option from a short, visible set
  • Toolbar controls: Preserve focus-only arrow navigation until activation

Basic implementation

Basic implementation

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';

class ViewToggle extends StatefulWidget {
  const ViewToggle({super.key});

  @override
  State<ViewToggle> createState() => _ViewToggleState();
}

class _ViewToggleState extends State<ViewToggle> {
  String _value = 'list';

  @override
  Widget build(BuildContext context) {
    return FortalToggleGroup<String>(
      items: const [
        RemixToggleGroupItem(
          value: 'list',
          label: 'List',
          icon: Icons.view_list,
        ),
        RemixToggleGroupItem(
          value: 'grid',
          label: 'Grid',
          icon: Icons.grid_view,
        ),
        RemixToggleGroupItem(
          value: 'board',
          label: 'Board',
          icon: Icons.view_kanban,
        ),
      ],
      selectedValue: _value,
      onChanged: (value) {
        if (value != null) setState(() => _value = value);
      },
      semanticLabel: 'View style',
    );
  }
}

Keyboard behavior

  • Tab enters on the selected item, or the first enabled item when none is selected
  • A second Tab leaves the group instead of visiting every item
  • Arrow Left / Arrow Right move focus in horizontal groups
  • Arrow Up / Arrow Down move focus in vertical groups
  • Home / End move focus to the first / last enabled item
  • Space / Enter activate the focused item
  • Arrow movement wraps when loop is true and clamps when false
  • Horizontal arrow direction follows the ambient text direction in RTL layouts

Arrow, Home, and End keys move focus only. Selection changes after explicit activation with Space, Enter, or a pointer.

Value and enabled-state contract

  • Item values are non-null, unique keys with stable equality and hash codes
  • selectedValue: null means no selection; otherwise it must match one item
  • An empty items list is valid when selectedValue is null
  • At most one item may set autofocus: true
  • onChanged: null disables the entire group, just like enabled: false

Rebuild with a new item list when values or ordering change. Do not mutate the list or a value's equality behavior while it is rendered.

Visible text supplies an item's accessible name. Icon-only items must provide a non-empty semanticLabel; an explicit semantic label replaces the visible label for accessibility services instead of being announced in addition to it.

Per-item styling

The group's item style is the default for every option. An item's style is merged afterward, so one option can add a local treatment while retaining group states and sizing.

The group styler controls structural container styling and context variants such as brightness, breakpoints, and text direction. Put hover, press, focus, selected, and disabled variants on ToggleGroupItemStyler; they resolve independently for each option. A raw styleSpec is authoritative and bypasses both the group and per-item fluent stylers.

Per-item style

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

Widget buildStatusToggleGroup({
  required String? value,
  required ValueChanged<String?> onChanged,
}) {
  return RemixToggleGroup<String>(
    items: [
      const RemixToggleGroupItem(value: 'draft', label: 'Draft'),
      RemixToggleGroupItem(
        value: 'published',
        label: 'Published',
        style: ToggleGroupItemStyler().labelColor(Colors.green).iconColor(Colors.green),
      ),
    ],
    selectedValue: value,
    onChanged: onChanged,
    style: ToggleGroupStyler().item(
      ToggleGroupItemStyler()
          .padding(.horizontal(12))
          .padding(.vertical(8))
          .onSelected(
            ToggleGroupItemStyler().color(Colors.green.shade50),
          ),
    ),
  );
}

Constructor

RemixToggleGroup

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

RemixToggleGroup<T> remixToggleGroupConstructor<T>({
  Key? key,
  required List<RemixToggleGroupItem<T>> items,
  required T? selectedValue,
  ValueChanged<T?>? onChanged,
  bool enabled = true,
  Axis orientation = Axis.horizontal,
  bool loop = true,
  String? semanticLabel,
  bool excludeSemantics = false,
  ToggleGroupStyler style = const ToggleGroupStyler.create(),
  ToggleGroupSpec? styleSpec,
}) => throw UnimplementedError();

RemixToggleGroupItem

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

RemixToggleGroupItem<T> remixToggleGroupItemConstructor<T>({
  required T value,
  String? label,
  IconData? icon,
  String? semanticLabel,
  bool enabled = true,
  FocusNode? focusNode,
  bool autofocus = false,
  ToggleGroupItemStyler style =
      const ToggleGroupItemStyler.create(),
}) => throw UnimplementedError();

Fortal widgets

FortalToggleGroup provides soft and surface variants plus three sizes. The recipe uses edge-to-edge items, a bordered surface container, selected accent colors, a visible keyboard focus ring, and disabled state styling.

See the fortalToggleGroupStyle source code for all available options.

View page source on GitHub

On this page