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.
| Factory | Render | Use |
|---|---|---|
Button.filled | FilledButton | Primary action |
Button.outlined | OutlinedButton | Secondary action |
Button.text | TextButton | Tertiary / link action |
Button.icon | IconButton | Icon-only — compact, isActive, IconButtonStyle.filled / .outlined / .standard |
Button.toggle | FilterChip | Boolean on/off chip |
// 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
| Param | Type | Default | Notes |
|---|---|---|---|
label | String | required (filled/outlined/text/toggle) | |
icon | IconData? | null | Optional leading icon |
onPressed | Future<void> Function()? | null | Async — auto-loading; null disables |
isLoading | bool | false | External loading override (vs. internal) |
destructive | bool | false | error palette, red border / fill |
compact (icon) | bool | false | 32 px square, zero padding |
isActive (icon) | bool | false | selectedTint background |
style (icon) | IconButtonStyle | standard | filled / 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.
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
| Style | Visual | When |
|---|---|---|
filled | Solid primary background on both halves | Single most important CTA on a surface |
outlined | One shared border around two borderless halves (default) | Secondary CTAs, dense rows |
text | No background, no border | In-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.
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
)| Variant | Visual |
|---|---|
subtle | surfaceBright floating pill on a dim track. Quiet — default. |
primary | Saturated 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.
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).
AlertsNotificationsButton(
unreadCount: store.pendingCount,
tooltip: 'Notifications',
onPressed: () => context.go('/notifications'),
)Cross-links
- Density & Sizing —
controlHeight,compactControlSize - Theming —
selectedTint, status palette - Dropdowns —
ActionMenuItemshape