Free copy-paste Card 008 for Flutter: a ready-to-use card widget with full Dart source code and a live preview. Works on mobile & desktop. No external packages.
Copy the Dart code below into a new file in your Flutter project (for example lib/widgets/card_008.dart) and use the widget anywhere in your widget tree.
import 'package:flutter/material.dart';
class Card008 extends StatefulWidget {
const Card008({
super.key,
this.title = '3D Card',
this.description =
'Lorem ipsum dolor sit amet, consectetur adipisicing elit.',
this.buttonText = 'SEE MORE',
this.month = 'JUNE',
this.day = '29',
this.cyanColor = const Color(0xFF04C1FA),
this.onTap,
});
final String title, description, buttonText, month, day;
final Color cyanColor;
final VoidCallback? onTap;
@override
State<Card008> createState() => _Card008State();
}
class _Card008State extends State<Card008>
with SingleTickerProviderStateMixin {
late final AnimationController _ctrl;
late final Animation<double> _anim;
bool _btnHover = false;
@override
void initState() {
super.initState();
_ctrl = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1000),
);
_anim = CurvedAnimation(parent: _ctrl, curve: Curves.easeOutCubic);
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
Widget _shift(double t, double x, double y, Widget child) =>
Transform.translate(offset: Offset(x * t, y * t), child: child);
@override
Widget build(BuildContext context) {
return MouseRegion(
opaque: true,
cursor: SystemMouseCursors.click,
onEnter: (_) => _ctrl.forward(),
onExit: (_) => _ctrl.reverse(),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: widget.onTap,
child: AnimatedBuilder(
animation: _anim,
builder: (context, _) {
final t = _anim.value;
final m = Matrix4.identity()
..setEntry(3, 2, 0.0012)
..rotateX(-0.16 * t)
..rotateY(-0.32 * t);
final c = widget.cyanColor;
return Transform(
alignment: Alignment.center,
transform: m,
child: Container(
width: 290,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: const Color(0xFFF0F0F0),
border: Border.all(color: Colors.white, width: 3),
borderRadius: BorderRadius.circular(4),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.15 + 0.20 * t),
blurRadius: 18 + 20 * t,
offset: Offset(-8 * t, 12 + 18 * t),
),
],
),
child: Stack(
clipBehavior: Clip.antiAlias,
children: [
Positioned.fill(
child: CustomPaint(
painter: _PerspectivePatternPainter(progress: t),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 52),
Container(
padding: const EdgeInsets.fromLTRB(22, 48, 22, 22),
color: c.withValues(alpha: 0.76),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_shift(
t,
4,
-2,
Text(
widget.title,
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w900,
),
),
),
const SizedBox(height: 10),
_shift(
t,
2,
-1,
Text(
widget.description,
style: const TextStyle(
color: Color(0xFFF2F2F2),
fontSize: 12,
fontWeight: FontWeight.w600,
height: 1.45,
),
),
),
const SizedBox(height: 16),
MouseRegion(
onEnter: (_) =>
setState(() => _btnHover = true),
onExit: (_) =>
setState(() => _btnHover = false),
child: AnimatedScale(
scale: _btnHover ? 1.06 : 1.0,
duration: const Duration(milliseconds: 1000),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(
alpha: 0.12,
),
blurRadius: 8,
offset: const Offset(0, 3),
),
],
),
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 8,
),
child: Text(
widget.buttonText,
style: TextStyle(
color: c,
fontSize: 10,
fontWeight: FontWeight.w900,
letterSpacing: 0.8,
),
),
),
),
),
],
),
),
],
),
Positioned(
top: 20,
right: 20,
child: _shift(
t,
8,
-6,
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: c, width: 1.5),
boxShadow: [
BoxShadow(
color: const Color(
0xFF64646F,
).withValues(alpha: 0.25 + 0.20 * t),
blurRadius: 10 + 12 * t,
offset: Offset(4 * t, 8 + 8 * t),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.month,
style: TextStyle(
color: c,
fontSize: 9,
fontWeight: FontWeight.w800,
),
),
Text(
widget.day,
style: TextStyle(
color: c,
fontSize: 20,
fontWeight: FontWeight.w900,
height: 1.1,
),
),
],
),
),
),
),
],
),
),
);
},
),
),
);
}
}
class _PerspectivePatternPainter extends CustomPainter {
const _PerspectivePatternPainter({this.progress = 0.0});
final double progress;
@override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Offset.zero & size);
final paint = Paint()
..color = const Color(0xFFE2E2E2)
..strokeWidth = 2.0;
final double shift = (progress * 30.0) % 24.0;
for (double x = -size.height - 24; x < size.width + 24; x += 24.0) {
canvas.drawLine(
Offset(x + shift, 0),
Offset(x + shift + size.height, size.height),
paint,
);
canvas.drawLine(
Offset(x - shift, size.height),
Offset(x - shift + size.height, 0),
paint,
);
}
}
@override
bool shouldRepaint(_PerspectivePatternPainter oldDelegate) =>
oldDelegate.progress != progress;
}