Free copy-paste Button 009 for Flutter: a ready-to-use button widget with full Dart source code and a live preview. Works on desktop. No external packages.
Copy the Dart code below into a new file in your Flutter project (for example lib/widgets/button_009.dart) and use the widget anywhere in your widget tree.
import 'package:flutter/material.dart';
class Button009 extends StatefulWidget {
const Button009({
super.key,
this.label = 'BUTTON',
this.onTap,
this.color = Colors.white,
this.textColor = Colors.black,
this.fontSize = 17.0,
this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 16),
this.rippleDuration = const Duration(milliseconds: 1000),
});
final String label;
final VoidCallback? onTap;
final Color color, textColor;
final double fontSize;
final EdgeInsetsGeometry padding;
final Duration rippleDuration;
@override
State<Button009> createState() => _Button009State();
}
class _Button009State extends State<Button009>
with SingleTickerProviderStateMixin {
late final AnimationController _pulse;
late final Animation<double> _scaleX, _scaleY, _opacity;
bool _hovered = false, _pressed = false;
@override
void initState() {
super.initState();
_pulse = AnimationController(vsync: this, duration: widget.rippleDuration);
final scaleCurve = CurvedAnimation(
parent: _pulse,
curve: Curves.easeOutQuad,
);
final opacityCurve = CurvedAnimation(parent: _pulse, curve: Curves.easeIn);
_scaleX = Tween<double>(begin: 1.0, end: 1.4).animate(scaleCurve);
_scaleY = Tween<double>(begin: 1.0, end: 1.6).animate(scaleCurve);
_opacity = Tween<double>(begin: 0.75, end: 0.0).animate(opacityCurve);
}
@override
void didUpdateWidget(covariant Button009 oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.rippleDuration != widget.rippleDuration) {
_pulse.duration = widget.rippleDuration;
}
}
@override
void dispose() {
_pulse.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final y = _pressed ? -1.0 : (_hovered ? -3.0 : 0.0);
final shadow = BoxShadow(
color: Colors.black.withValues(alpha: _hovered ? 0.22 : 0.08),
blurRadius: _pressed ? 10.0 : (_hovered ? 20.0 : 6.0),
offset: Offset(0, _pressed ? 5.0 : (_hovered ? 10.0 : 2.0)),
);
return MouseRegion(
opaque: true,
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() => _hovered = true);
_pulse.forward(from: 0.0);
},
onExit: (_) {
setState(() => _hovered = false);
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (_) {
setState(() => _pressed = true);
_pulse.forward(from: 0.0);
},
onTapUp: (_) {
setState(() => _pressed = false);
widget.onTap?.call();
},
onTapCancel: () => setState(() => _pressed = false),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
transform: Matrix4.translationValues(0.0, y, 0.0),
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
AnimatedBuilder(
animation: _pulse,
builder: (context, _) {
if (!_pulse.isAnimating && !_hovered) {
return const SizedBox.shrink();
}
return IgnorePointer(
child: Transform.scale(
scaleX: _scaleX.value,
scaleY: _scaleY.value,
child: Opacity(
opacity: _opacity.value.clamp(0.0, 1.0),
child: DecoratedBox(
decoration: BoxDecoration(
color: widget.color,
borderRadius: BorderRadius.circular(100),
),
child: Padding(
padding: widget.padding,
child: Text(
widget.label.toUpperCase(),
style: TextStyle(
color: Colors.transparent,
fontSize: widget.fontSize,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
);
},
),
AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
padding: widget.padding,
decoration: BoxDecoration(
color: widget.color,
borderRadius: BorderRadius.circular(100),
boxShadow: [shadow],
),
child: Text(
widget.label.toUpperCase(),
style: TextStyle(
color: widget.textColor,
fontSize: widget.fontSize,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
),
);
}
}