Alert Widget
Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger
View component →A clean, card-style accordion where each panel is a self-contained bordered card with a smoothly rotating chevron. Only one panel stays open at a time and the body height animates open and closed — perfect for FAQs and grouped settings. Built with pure Flutter SDK, no external packages.
What's Included
Use Cases
PRO TIP
For long lists of accordion panels, wrap the column in a ListView.builder and lift the open index into the parent so only one item rebuilds on tap. Use AnimatedSize (not a fixed height) so the body grows to fit any amount of text without overflow.
Copy this into your Flutter project. No external packages required.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.white,
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 480),
child: AccordionCard(
items: const [
AccordionItem(
title: 'What is FlutterKit?',
body:
'FlutterKit is an open-source library of production-ready '
'Flutter widgets — buttons, dialogs, navbars, and more. '
'Copy the code, paste it in, and ship faster.',
),
AccordionItem(
title: 'Is there a Figma file available?',
body:
'Yes. Every component is designed in Figma first, so you '
'get a pixel-perfect design reference alongside the code.',
),
AccordionItem(
title: 'Can I use it in commercial projects?',
body:
'Absolutely. All components are MIT licensed and free to '
'use in personal and commercial apps, forever.',
),
],
),
),
),
),
),
);
}
}
/// A single panel's content.
class AccordionItem {
final String title;
final String body;
const AccordionItem({required this.title, required this.body});
}
/// Card-style accordion. Only one panel is open at a time.
/// Pass [initialOpen] = -1 to start with everything collapsed.
class AccordionCard extends StatefulWidget {
final List<AccordionItem> items;
final int initialOpen;
const AccordionCard({
super.key,
required this.items,
this.initialOpen = 0,
});
@override
State<AccordionCard> createState() => _AccordionCardState();
}
class _AccordionCardState extends State<AccordionCard> {
late int _openIndex = widget.initialOpen;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < widget.items.length; i++)
Padding(
padding: EdgeInsets.only(top: i == 0 ? 0 : 16),
child: _AccordionPanel(
item: widget.items[i],
isOpen: _openIndex == i,
onTap: () => setState(
() => _openIndex = _openIndex == i ? -1 : i,
),
),
),
],
);
}
}
class _AccordionPanel extends StatelessWidget {
final AccordionItem item;
final bool isOpen;
final VoidCallback onTap;
const _AccordionPanel({
required this.item,
required this.isOpen,
required this.onTap,
});
static const Color _border = Color(0xFFE5E7EB);
static const Color _heading = Color(0xFF111827);
static const Color _body = Color(0xFF4B5563);
@override
Widget build(BuildContext context) {
final BorderRadius headerRadius = isOpen
? const BorderRadius.vertical(top: Radius.circular(8))
: BorderRadius.circular(8);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header button
Material(
color: Colors.white,
borderRadius: headerRadius,
child: InkWell(
borderRadius: headerRadius,
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: headerRadius,
border: Border.all(color: _border),
boxShadow: isOpen
? null
: const [
BoxShadow(
color: Color(0x0A000000),
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: Row(
children: [
Expanded(
child: Text(
item.title,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: isOpen ? _heading : _body,
),
),
),
const SizedBox(width: 12),
AnimatedRotation(
duration: const Duration(milliseconds: 200),
turns: isOpen ? 0.5 : 0,
child: const Icon(
Icons.keyboard_arrow_down,
size: 20,
color: _body,
),
),
],
),
),
),
),
// Body — height animates open/closed
AnimatedCrossFade(
duration: const Duration(milliseconds: 200),
firstCurve: Curves.easeOut,
secondCurve: Curves.easeOut,
sizeCurve: Curves.easeInOut,
crossFadeState:
isOpen ? CrossFadeState.showFirst : CrossFadeState.showSecond,
firstChild: Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
left: BorderSide(color: _border),
right: BorderSide(color: _border),
bottom: BorderSide(color: _border),
),
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(8),
),
),
child: Text(
item.body,
style: const TextStyle(
fontSize: 14,
height: 1.6,
color: _body,
),
),
),
secondChild: const SizedBox(width: double.infinity),
),
],
);
}
}Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger
View component →Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress
View component →Payment QR + barcode card with header, tabs, and security footer
View component →