Skip to content

Buttons

CDX UI ships one unified Button sealed class plus three button-shaped controls: SplitActionButton, CdxSegmentedTabs, and the small CdxDismissAffordance glyph used inside chips and inputs.

All read CdxConfig.controlHeight, controlBorderRadius, and the canonical interaction tokens (hoverTint, selectedTint).

Button

Button is a sealed class with five factories. Every variant auto-handles its own loading state — pass an async callback and the button shows a spinner while it's in flight.

FactoryRenderUse
Button.filledFilledButtonPrimary action
Button.outlinedOutlinedButtonSecondary action
Button.textTextButtonTertiary / link action
Button.iconIconButtonIcon-only — compact, isActive, IconButtonStyle.filled / .outlined / .standard
Button.toggleFilterChipBoolean on/off chip
dart
// Primary action with async loading
Button.filled(
  label: 'Save',
  icon: FluentIcons.save_24_regular,
  onPressed: () async => store.save(),
)

// Destructive secondary
Button.outlined(
  label: 'Delete',
  icon: FluentIcons.delete_24_regular,
  destructive: true,
  onPressed: () async => store.delete(),
)

// Compact icon button (toolbar / list row)
Button.icon(
  icon: FluentIcons.edit_24_regular,
  compact: true,
  tooltip: 'Edit',
  onPressed: () async => editor.open(),
)

// Active toolbar toggle (current selection)
Button.icon(
  icon: FluentIcons.list_24_regular,
  isActive: viewMode == ViewMode.list,
  onPressed: () async => store.setView(ViewMode.list),
)

// Boolean filter chip
Button.toggle(
  label: 'Active only',
  icon: FluentIcons.filter_24_regular,
  isActive: filter.activeOnly,
  onChanged: (v) => filter.setActiveOnly(v),
)

Parameters

ParamTypeDefaultNotes
labelStringrequired (filled/outlined/text/toggle)
iconIconData?nullOptional leading icon
onPressedFuture<void> Function()?nullAsync — auto-loading; null disables
isLoadingboolfalseExternal loading override (vs. internal)
destructiveboolfalseerror palette, red border / fill
compact (icon)boolfalse32 px square, zero padding
isActive (icon)boolfalseselectedTint background
style (icon)IconButtonStylestandardfilled / outlined / standard
tooltip (icon)String?null

Why a sealed class?

Switch-exhaustive factories prevent the "27-prop button" trap. Each variant carries only the params that make sense for it.

SplitActionButton

A primary action visually joined to a caret menu — three styles, two menu modes.

dart
SplitActionButton(
  label: 'Save',
  icon: FluentIcons.save_24_regular,
  onPressed: _save,
  style: SplitActionButtonStyle.outlined,
  menuItems: [
    ActionMenuItem.action(title: 'Save as draft', onTap: _saveDraft),
    ActionMenuItem.action(title: 'Save and duplicate', onTap: _saveAndDup),
  ],
)

// Custom menu body — pass menuBuilder + empty menuItems
SplitActionButton(
  icon: FluentIcons.filter_24_regular,
  menuItems: const [],
  menuBuilder: (context) => MyFilterPresetPopover(),
)

SplitActionButtonStyle

StyleVisualWhen
filledSolid primary background on both halvesSingle most important CTA on a surface
outlinedOne shared border around two borderless halves (default)Secondary CTAs, dense rows
textNo background, no borderIn-card quick actions, repeated row actions

compact: true forces 32 px height (icon-only mode implies compact automatically).

CdxSegmentedTabs

Pill-style segmented tab bar with an animated sliding indicator. Mirrors Flutter's TabBar shape (tabs + value + onChanged + isScrollable) but renders as a single rounded container.

dart
CdxSegmentedTabs<BehaviorType>(
  tabs: [
    CdxSegmentedTab(
      value: BehaviorType.cleaning,
      label: const Text('Cleaning'),
      icon: FluentIcons.broom_24_regular,
    ),
    CdxSegmentedTab(value: BehaviorType.usage, label: const Text('Usage')),
    CdxSegmentedTab(
      value: BehaviorType.problem,
      label: const Text('Problem'),
      badge: const _RedDot(),
    ),
  ],
  value: selected,
  onChanged: (v) => setState(() => selected = v),
  isScrollable: true,
  variant: CdxSegmentedTabsVariant.subtle, // .primary for top-of-page workspace switcher
)
VariantVisual
subtlesurfaceBright floating pill on a dim track. Quiet — default.
primarySaturated primary fill with shadow. Loud — use for top-of-page switchers.

CdxSegmentedTab.label is a Widget?; pass Text(...) for ordinary labels, a small Row when the label needs an inline count chip, or null for an icon-only tab. It also carries optional badge (corner-pinned, IgnorePointer), icon, tooltip, loading, and the typed value.

CdxDismissAffordance

Compact close glyph used inside chips, search inputs, token fields, and parenthesized filter groups. Avoids IconButton's 48 px floor.

dart
CdxDismissAffordance(
  icon: FluentIcons.dismiss_12_regular,
  size: CdxDismissAffordanceSize.compact, // .regular (6 px hit padding) for standalone use
  tooltip: 'Remove',
  onPressed: () => removeChip(),
)

AlertsNotificationsButton

Bell IconButton with an unread-count Badge (suppressed when count is null/0; shows 99+ past 99).

dart
AlertsNotificationsButton(
  unreadCount: store.pendingCount,
  tooltip: 'Notifications',
  onPressed: () => context.go('/notifications'),
)