RiseUI
Components

TagGroup

Focusable tag list with selection, sizes, variants, optional icons and removal — aligned with @heroui/react and tag-group.css.

Usage

Default: single selection with leading icons (HeroUI Default story).

RiseTagGroup<String>(
  selectionMode: RiseTagGroupSelectionMode.single,
  values: ['news', 'travel', 'gaming', 'shopping'],
  leadingBuilder: (v) => Icon(/* article · planet · rocket · bag */, size: 16),
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  onChanged: (next) => setState(() => selected = next),
)

Sizes

sm · md · lg with Label captions.

RiseTagGroup<String>(
  selectionMode: RiseTagGroupSelectionMode.single,
  size: RiseTagSize.sm,
  values: ['news', 'travel', 'gaming'],
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  onChanged: (next) => setState(() => selected = next),
)

Variants

default (outlined) vs surface (muted fill).

RiseTagGroup<String>(
  selectionMode: RiseTagGroupSelectionMode.single,
  variant: RiseTagVariant.surface,
  values: ['news', 'travel', 'gaming'],
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  onChanged: (next) => setState(() => selected = next),
)

Disabled

Per-tag disabledKeys and helper copy below the list.

RiseTagGroup<String>(
  disabledKeys: {'news', 'gaming'},
  values: ['news', 'travel', 'gaming'],
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  description: 'Some tags are disabled',
  onChanged: (next) => setState(() => selected = next),
)

RiseTagGroup<String>(
  disabledKeys: {'travel'},
  values: ['news', 'travel', 'gaming'],
  labelBuilder: (v) => capitalize(v),
  selected: selectedB,
  description: 'Tags disabled via disabledKeys prop',
  onChanged: (next) => setState(() => selectedB = next),
)

Selection modes

Single vs multiple with helper text.

RiseTagGroup<String>(
  selectionMode: RiseTagGroupSelectionMode.single,
  values: ['news', 'travel', 'gaming', 'shopping'],
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  onChanged: (next) => setState(() => selected = next),
)

Controlled

External selection state with summary line.

RiseTagGroup<String>(
  selectionMode: RiseTagGroupSelectionMode.multiple,
  label: 'Categories (controlled)',
  values: ['news', 'travel', 'gaming', 'shopping'],
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  onChanged: (next) => setState(() => selected = next),
)

With prefix

Icons and avatars (HeroUI WithPrefix).

RiseTagGroup<String>(
  selectionMode: RiseTagGroupSelectionMode.single,
  leadingBuilder: (v) => Icon(iconFor(v), size: 16),
  values: ['news', 'travel', 'gaming', 'shopping'],
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  description: 'Tags with icons',
  onChanged: (next) => setState(() => selected = next),
)

With remove button

Removable lists and empty state.

RiseTagGroup<String>(
  values: values,
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  onChanged: (next) => setState(() => selected = next),
  onRemove: (v) => setState(() => values.remove(v)),
)

With error message

Label, dynamic description after tags, and validation line.

RiseTagGroup<String>(
  label: 'Amenities',
  description: invalid ? 'Select at least one category' : 'Selected: …',
  errorMessage: invalid ? 'Please select at least one category' : null,
  values: ['laundry', 'fitness', 'parking'],
  labelBuilder: (v) => capitalize(v),
  selected: selected,
  onChanged: (next) => setState(() => selected = next),
)

With list data

Avatars, removal, and selected summary (HeroUI WithListData).

RiseTagGroup<String>(
  label: 'Team Members',
  selectionMode: RiseTagGroupSelectionMode.multiple,
  values: ids,
  leadingBuilder: (id) => RiseAvatar(
    size: RiseAvatarSize.sm,
    image: NetworkImage(avatarUrlFor(id)),
    name: nameFor(id),
  ),
  labelBuilder: (id) => nameFor(id),
  selected: selected,
  description: 'Select team members for your project',
  onChanged: (next) => setState(() => selected = next),
  onRemove: (id) => setState(() => removeUser(id)),
)