Tile

Select Tile Group

A group of tiles that allow users to make a selection from a set of options.

For desktop, a select group is generally recommended over this.

enum Sidebar { recents, home, applications }

FSelectTileGroup<Sidebar>(
  control: const FMultiValueControl.managed(initial: {Sidebar.recents}),
  label: const Text('Sidebar'),
  description: const Text('These will be shown in the sidebar.'),
  children: const [
    FSelectTile(title: Text('Recents'), suffix: Icon(FIcons.timer), value: Sidebar.recents),
    FSelectTile(title: Text('Home'), suffix: Icon(FIcons.house), value: Sidebar.home),
    FSelectTile(title: Text('Applications'), suffix: Icon(FIcons.appWindowMac), value: Sidebar.applications),
  ],
);

CLI

To generate and customize this style:

dart run forui style create select-tile-group

Usage

FSelectTileGroup(...)

FSelectTileGroup<Value>(
  children: [
    FSelectTile(title: const Text('Option 1'), value: Value.option1),
  ],
  control: FMultiValueControl.managed(initial: {Value.option1}),
  scrollController: ScrollController(),
  style: FTileGroupStyle(...),
  cacheExtent: 100,
  maxHeight: 200,
  dragStartBehavior: DragStartBehavior.start,
  physics: const ClampingScrollPhysics(),
  divider: FItemDivider.indented,
  label: const Text('Sidebar'),
  description: const Text('Select the items you want to display in the sidebar.'),
  semanticsLabel: 'Sidebar',
  errorBuilder: (context, error) => Text(error),
  onSaved: (values) {},
  onReset: () {},
  validator: (values) => values?.isEmpty ?? true ? 'Select an item' : null,
  forceErrorText: null,
  enabled: true,
  autovalidateMode: AutovalidateMode.disabled,
);

FSelectTileGroup.builder(...)

FSelectTileGroup<int>.builder(
  tileBuilder: (context, index) => FSelectTile(title: Text('Tile $index'), value: index),
  count: 100,
  control: FMultiValueControl.managed(initial: {1}),
  scrollController: ScrollController(),
  style: FTileGroupStyle(...),
  cacheExtent: 100,
  maxHeight: 200,
  dragStartBehavior: DragStartBehavior.start,
  physics: const ClampingScrollPhysics(),
  divider: FItemDivider.indented,
  label: const Text('Settings'),
  description: const Text('Personalize your experience'),
  semanticsLabel: 'Settings',
  errorBuilder: (context, error) => Text(error),
  onSaved: (values) {},
  onReset: () {},
  validator: (values) => values?.isEmpty ?? true ? 'Select an item' : null,
  forceErrorText: null,
  enabled: true,
  autovalidateMode: AutovalidateMode.disabled,
);

Examples

Behavior

Scrollable

enum Sidebar { recents, home, applications }

FSelectTileGroup<Sidebar>(
  control: const FMultiValueControl.managed(initial: {Sidebar.recents}),
  label: const Text('Sidebar'),
  description: const Text('These will be shown in the sidebar.'),
  maxHeight: 100,
  children: const [
    FSelectTile(title: Text('Recents'), suffix: Icon(FIcons.timer), value: Sidebar.recents),
    FSelectTile(title: Text('Home'), suffix: Icon(FIcons.house), value: Sidebar.home),
    FSelectTile(title: Text('Applications'), suffix: Icon(FIcons.appWindowMac), value: Sidebar.applications),
  ],
);

Lazy Scrollable

FSelectTileGroup<int>.builder(
  control: const FMultiValueControl.managed(initial: {1}),
  label: const Text('Applicable values'),
  maxHeight: 200,
  tileBuilder: (context, index) => FSelectTile(title: Text('Tile $index'), value: index),
);

Multi-value Form

enum Language { dart, java, rust, python }

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

  @override
  State<MultiValueForm> createState() => _MultiValueFormState();
}

class _MultiValueFormState extends State<MultiValueForm> {
  final _key = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) => Form(
    key: _key,
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.start,
      spacing: 20,
      children: [
        FSelectTileGroup<Language>(
          label: const Text('Favorite Languages'),
          description: const Text('Your favorite language.'),
          validator: (values) => (values?.isEmpty ?? true) ? 'Please select at least one language.' : null,
          children: const [
            FSelectTile(title: Text('Dart'), value: Language.dart),
            FSelectTile(title: Text('Java'), value: Language.java),
            FSelectTile(title: Text('Rust'), value: Language.rust),
            FSelectTile(title: Text('Python'), value: Language.python),
          ],
        ),
        FButton(
          child: const Text('Save'),
          onPress: () {
            if (!_key.currentState!.validate()) {
              // Handle errors here.
              return;
            }

            _key.currentState!.save();
            // Do something.
          },
        ),
      ],
    ),
  );
}

Radio Form

enum Notification { all, direct, nothing }

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

  @override
  State<RadioForm> createState() => _RadioFormState();
}

class _RadioFormState extends State<RadioForm> {
  final _key = GlobalKey<FormState>();
  final _controller = FMultiValueNotifier<Notification>.radio();

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

  @override
  Widget build(BuildContext context) => Form(
    key: _key,
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.start,
      spacing: 20,
      children: [
        FSelectTileGroup<Notification>(
          control: FMultiValueControl.managed(controller: _controller),
          label: const Text('Notifications'),
          description: const Text('Select the notifications.'),
          validator: (values) => values?.isEmpty ?? true ? 'Please select a value.' : null,
          children: const [
            FSelectTile(title: Text('All new messages'), value: Notification.all),
            FSelectTile(title: Text('Direct messages and mentions'), value: Notification.direct),
            FSelectTile(title: Text('Nothing'), value: Notification.nothing),
          ],
        ),
        FButton(
          child: const Text('Save'),
          onPress: () {
            if (!_key.currentState!.validate()) {
              // Handle errors here.
              return;
            }

            _key.currentState!.save();
            // Do something.
          },
        ),
      ],
    ),
  );
}

Appearance

Custom Icons

FSelectTileGroup<String>(
  control: const FMultiValueControl.managedRadio(),
  label: const Text('Settings'),
  children: const [
    FSelectTile.suffix(prefix: Icon(FIcons.list), title: Text('List View'), value: 'List'),
    FSelectTile.suffix(prefix: Icon(FIcons.layoutGrid), title: Text('Grid View'), value: 'Grid'),
  ],
);

Full Divider

enum Sidebar { recents, home, applications }

FSelectTileGroup<Sidebar>(
  control: const FMultiValueControl.managed(initial: {Sidebar.recents}),
  divider: FItemDivider.full,
  label: const Text('Sidebar'),
  description: const Text('These will be shown in the sidebar.'),
  children: const [
    FSelectTile(title: Text('Recents'), suffix: Icon(FIcons.timer), value: Sidebar.recents),
    FSelectTile(title: Text('Home'), suffix: Icon(FIcons.house), value: Sidebar.home),
    FSelectTile(title: Text('Applications'), suffix: Icon(FIcons.appWindowMac), value: Sidebar.applications),
  ],
);

No Divider

enum Sidebar { recents, home, applications }

FSelectTileGroup<Sidebar>(
  control: const FMultiValueControl.managed(initial: {Sidebar.recents}),
  divider: FItemDivider.none,
  label: const Text('Sidebar'),
  description: const Text('These will be shown in the sidebar.'),
  children: const [
    FSelectTile(title: Text('Recents'), suffix: Icon(FIcons.timer), value: Sidebar.recents),
    FSelectTile(title: Text('Home'), suffix: Icon(FIcons.house), value: Sidebar.home),
    FSelectTile(title: Text('Applications'), suffix: Icon(FIcons.appWindowMac), value: Sidebar.applications),
  ],
);

On this page