Components
Button
A versatile button component with multiple variants and sizes
Installation
npx fluttercn@latest add buttonUsage
Variants
Buttons come in six different variants for different use cases:

// Primary - Main call-to-action
Button(
onPressed: () {},
child: Text('Primary'),
)
// Secondary - Less prominent actions
Button(
onPressed: () {},
variant: ButtonVariant.secondary,
child: Text('Secondary'),
)
// Destructive - Dangerous actions
Button(
onPressed: () {},
variant: ButtonVariant.destructive,
child: Text('Destructive'),
)
// Outline - Alternative style
Button(
onPressed: () {},
variant: ButtonVariant.outline,
child: Text('Outline'),
)
// Ghost - Minimal style
Button(
onPressed: () {},
variant: ButtonVariant.ghost,
child: Text('Ghost'),
)
// Link - Text link style
Button(
onPressed: () {},
variant: ButtonVariant.link,
child: Text('Link'),
)
Sizes
Three predefined sizes plus an icon-only size:

// Small
Button(
onPressed: () {},
size: ButtonSize.sm,
child: Text('Small'),
)
// Medium (default)
Button(
onPressed: () {},
size: ButtonSize.md,
child: Text('Medium'),
)
// Large
Button(
onPressed: () {},
size: ButtonSize.lg,
child: Text('Large'),
)
// Icon only
Button(
onPressed: () {},
size: ButtonSize.icon,
child: Icon(Icons.settings),
)With Icons
Add leading or trailing icons to buttons:
// Leading icon (default)
Button(
onPressed: () {},
icon: Icon(Icons.mail),
child: Text('Email'),
)
// Trailing icon
Button(
onPressed: () {},
icon: Icon(Icons.arrow_forward),
iconPosition: IconPosition.trailing,
child: Text('Continue'),
)
Loading State
Show loading indicator while processing:

Button(
onPressed: () {},
loading: true,
child: Text('Loading...'),
)Disabled State
Disable button interactions:

API Reference
Button
| Property | Type | Default | Description |
|---|---|---|---|
onPressed | VoidCallback? | required | Callback when button is pressed |
child | Widget | required | Button content (typically Text widget) |
variant | ButtonVariant | primary | Visual style variant |
size | ButtonSize | md | Button size |
disabled | bool | false | Whether button is disabled |
loading | bool | false | Show loading indicator |
fullWidth | bool | false | Expand to fill container width |
icon | Widget? | null | Optional icon |
iconPosition | IconPosition | leading | Icon placement (leading or trailing) |
ButtonVariant
primary- Filled button with primary color (main actions)secondary- Filled button with secondary color (less emphasis)destructive- Filled button with error color (dangerous actions)outline- Transparent with border (alternative actions)ghost- Minimal style with hover effect (subtle actions)link- Text-only link style (navigation)
ButtonSize
sm- Small (32px height)md- Medium (40px height)lg- Large (48px height)icon- Icon only (40px height, square)
IconPosition
leading- Icon before texttrailing- Icon after text
Features
- Six Variants - Different styles for different contexts
- Multiple Sizes - From small to large plus icon-only
- Loading States - Built-in loading spinner
- Icons - Leading or trailing icon support
- Animations - Press animation and smooth transitions
- Themed - Automatically adapts to light/dark mode
- Accessible - Proper disabled and loading states
Examples
Delete Confirmation
Column(
children: [
Text('Are you sure you want to delete this item?'),
AppTheme.gapLg,
Row(
children: [
Expanded(
child: Button(
onPressed: () {},
variant: ButtonVariant.outline,
fullWidth: true,
child: Text('Cancel'),
),
),
AppTheme.gapHorizontalMd,
Expanded(
child: Button(
onPressed: () {},
variant: ButtonVariant.destructive,
fullWidth: true,
child: Text('Delete'),
),
),
],
),
],
)Navigation Button
Button(
onPressed: () {
Navigator.push(context, ...);
},
variant: ButtonVariant.link,
icon: Icon(Icons.arrow_forward),
iconPosition: IconPosition.trailing,
child: Text('Learn More'),
)Loading Button
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool _isLoading = false;
Future<void> _handleSubmit() async {
setState(() => _isLoading = true);
await Future.delayed(Duration(seconds: 2));
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return Button(
onPressed: _handleSubmit,
loading: _isLoading,
child: Text('Submit'),
);
}
}Icon Button
Button(
onPressed: () {},
size: ButtonSize.icon,
variant: ButtonVariant.ghost,
child: Icon(Icons.more_vert),
)Buttons automatically scale down when pressed and show appropriate cursor on hover.
When loading is true, the button becomes non-interactive. Set loading to
false when the operation completes.
Best Practices
- Use
primaryfor main actions (submit, save, continue) - Use
secondaryfor alternative actions - Use
destructivefor dangerous actions (delete, remove) - Use
outlinefor secondary actions with more emphasis than ghost - Use
ghostfor tertiary actions or toolbar buttons - Use
linkfor navigation or less prominent actions - Always provide meaningful text in the
childwidget - Use
disabledstate for unavailable actions - Show
loadingstate during async operations