A versatile button component supporting multiple variants, sizes, icon positions, loading state, and destructive mode. Ported from McButton (MColi UI) and Button (base shadcn variant).
registry/ui/mc-button.tsx— McButton (primary/secondary/tertiary/link)components/ui/button.tsx— Base Button (default/outline/secondary/ghost/destructive/link)
MColi UI's button system uses class-variance-authority for variant management and @base-ui/react for accessibility primitives. The button supports leading/trailing/dot/only icon modes and a loading spinner.
const FluxButton({
super.key,
this.variant = FluxButtonVariant.primary,
this.size = FluxSize.md,
this.leading,
this.trailing,
this.icon,
this.loading = false,
this.destructive = false,
this.onPressed,
this.style,
this.semanticLabel,
required this.child,
});| Property | Type | Default | Description |
|---|---|---|---|
| variant | FluxButtonVariant | primary | Visual style variant |
| size | FluxSize | md | Button size |
| leading | Widget? | null | Leading icon |
| trailing | Widget? | null | Trailing icon |
| icon | FluxButtonIcon? | null | Icon mode (leading/trailing/dot/only) |
| loading | bool | false | Show loading spinner |
| destructive | bool | false | Destructive color override |
| onPressed | VoidCallback? | null | Tap callback |
| style | FluxButtonStyle? | null | Custom style override |
| semanticLabel | String? | null | Accessibility label |
| child | Widget | - | Button label content |
enum FluxButtonVariant {
primary, // Filled brand color
secondary, // Soft brand background
tertiary, // Subtle brand tint
link, // Text-only, underline
outline, // Bordered (from base Button)
ghost, // Transparent, hover only (from base Button)
destructive,// Error/danger (from base Button)
}enum FluxSize {
xs, // 24px height
sm, // 28px height
md, // 32px height
lg, // 36px height
xl, // 40px height
}| State | Visual |
|---|---|
| Default | Normal colors, no effects |
| Hover | Slightly darker/lighter background |
| Pressed/Active | Ring indicator + background shift |
| Focused | Focus ring (--ring color) |
| Loading | Spinner replaces content |
| Disabled | 50% opacity, no pointer events |
enum FluxButtonIcon {
leading, // Icon before text
trailing, // Icon after text
dot, // Small dot indicator
only, // Icon-only (square button)
}- Transition:
all 150msease - Press: Subtle Y-translate (
translate-y-px) - Ring: 4px ring on active
- Spinner: Continuous rotation (used for loading)
- Built on
Buttonfrom@base-ui/react(ARIA button role) - Keyboard: Enter/Space to activate
- Focus-visible ring
- Disabled state prevents interaction
semanticLabelfor icon-only buttons
// Primary with leading icon
FluxButton(
variant: FluxButtonVariant.primary,
leading: Icon(Icons.add),
onPressed: () {},
child: Text('Create'),
)
// Loading state
FluxButton(
loading: true,
onPressed: () {},
child: Text('Saving...'),
)
// Icon only
FluxButton(
icon: FluxButtonIcon.only,
leading: Icon(Icons.plus),
onPressed: () {},
)
// Destructive variant
FluxButton(
destructive: true,
variant: FluxButtonVariant.primary,
onPressed: () {},
child: Text('Delete'),
)
// Link variant with trailing icon
FluxButton(
variant: FluxButtonVariant.link,
trailing: Icon(Icons.arrow_forward),
onPressed: () {},
child: Text('Learn more'),
)Do:
- Use
primaryfor main CTAs,secondaryfor alternative actions - Add
semanticLabelto icon-only buttons - Show
loadingduring async operations
Don't:
- Stack multiple
primarybuttons - Use
linkvariant in dense layouts (too subtle) - Omit text for icon-only buttons without a tooltip
constconstructor for static buttons- Builder pattern available for dynamic content
- Minimal widget rebuilds via separation of style vs content
- Flutter's
TextButton,ElevatedButton,OutlinedButtonprovide similar primitives; FluxButton wraps them with consistent styling - Use
InkWellorGestureDetectorwithMaterialfor ripple - Variants map to M3 Button styles but override colors
- Add
FluxButtonGroupfor segmented button groups - Add dropdown/toggle button variants
- Support custom loading widget