easy_expandable 0.1.0
easy_expandable: ^0.1.0 copied to clipboard
Wrap any widget so it smoothly expands and collapses when its header is tapped. Zero-boilerplate, controlled or uncontrolled, with lazy body building.
easy_expandable #
Wrap any widget so it smoothly expands and collapses when its header is tapped. No controllers to wire up, no boilerplate — but full control when you want it.
- 🎯 Wrap anything — the header and the body are your widgets.
- 🪗 Smooth reveal — the body slides out from under a clip (no fade flicker).
- 🎛️ Controlled or uncontrolled — let the widget own its state, or drive it yourself (great for accordions).
- 💤 Lazy by default — a collapsed body is not built at all, unless you ask
to keep it with
maintainState. - ♿ Accessible — the header is exposed as an expandable button to screen readers.
Getting started #
dependencies:
easy_expandable: ^0.1.0
import 'package:easy_expandable/easy_expandable.dart';
Usage #
Uncontrolled — the simplest case #
The widget remembers whether it is open. Just give it a starting value.
Expandable(
initiallyExpanded: false,
header: (context, isExpanded) => ListTile(
title: const Text('Tap me'),
trailing: Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
),
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('Anything can go here.'),
),
)
A standard header, without the boilerplate #
The header is always your widget — but for the common "title + chevron" case
you can drop in ExpandableHeader instead of wiring the rotating icon yourself.
Full custom headers stay exactly as above; this is purely opt-in.
Expandable(
header: (context, isExpanded) => ExpandableHeader(
leading: const Icon(Icons.folder_outlined),
title: const Text('Section'),
isExpanded: isExpanded, // drives the auto-rotating chevron
),
child: const Text('Body'),
)
Controlled — you own the state #
Pass isExpanded and update it in onExpansionChanged. This is what lets you
coordinate several tiles, persist state, or open one from a button elsewhere.
bool _open = false;
Expandable(
isExpanded: _open,
onExpansionChanged: (value) => setState(() => _open = value),
header: (context, isExpanded) => const Text('Controlled header'),
child: const Text('Body'),
)
In controlled mode the body only moves when the parent updates
isExpanded. If you forget to callsetState, the tap is reported but nothing opens.
With a controller — drive it programmatically #
Skip the bool and setState entirely: give it an ExpandableController and
call expand(), collapse(), or toggle() from anywhere.
final controller = ExpandableController(); // dispose it in State.dispose()
Expandable(
controller: controller,
header: (context, isExpanded) => const Text('Controller-driven'),
child: const Text('Body'),
)
// elsewhere:
controller.toggle();
Accordion — a group of tiles #
ExpandableGroup coordinates several entries. By default it is an accordion
(one open at a time); pass allowMultiple: true to let several stay open.
ExpandableGroup(
// controller: myGroupController, // optional; for collapseAll()/expandAll()
separatorBuilder: (context, i) => const Divider(height: 1),
children: [
for (final section in sections)
ExpandableGroupChild(
header: (context, isExpanded) => Text(section.title),
body: Text(section.body),
),
],
)
Parameters #
| Parameter | Type | Default | Description |
|---|---|---|---|
header |
Widget Function(context, bool isExpanded) |
required | Always-visible, tappable header. |
child |
Widget |
required | Content revealed when expanded. |
initiallyExpanded |
bool |
false |
Starting value (uncontrolled mode). |
isExpanded |
bool? |
null |
Non-null switches to controlled mode. |
onExpansionChanged |
ValueChanged<bool>? |
null |
Called with the requested state on tap. |
duration |
Duration |
200ms |
Animation duration. |
curve |
Curve |
Curves.easeOut |
Animation curve. |
maintainState |
bool |
false |
Keep the body built while collapsed. |
crossAxisAlignment |
CrossAxisAlignment |
.start |
Column alignment. |
revealAlignment |
Alignment |
.topCenter |
Where the body slides out from. |
Additional information #
Issues and contributions are welcome on the issue tracker.