expandable_plus 1.2.4 copy "expandable_plus: ^1.2.4" to clipboard
expandable_plus: ^1.2.4 copied to clipboard

Expandable and collapsible panels with accordion groups for Flutter. A maintained, drop-in successor to the expandable package; adds header padding, fixes body-tap toggling.

expandable_plus #

Panels that expand and collapse, cross-fading between two different views rather than clipping one, with accordion groups and a correct screen reader announcement.

An accordion of three panels, Shipping, Payment and Returns. Opening one
closes the last, the chevron turns as it goes, and the collapsed and expanded
states show different content rather than the same content clipped

Why this instead of what you already have #

Instead of ExpansionTile. Its constructor takes no collapsed-content parameter (material/expansion_tile.dart:121). Every collapsed* field it does accept is a style: collapsedBackgroundColor, collapsedTextColor, collapsedIconColor, collapsedShape. It reveals a single body by animating a height factor, so there is no cross-fade between two different views, and nothing coordinates one tile with the next.

Two rows of three frames. The top row reveals one body by growing its height,
so the shut frame shows nothing. The bottom row fades a summary line out while a
form fades in, and the shut frame still says
something.

That difference decides what a shut panel can say. Growing a height means the collapsed state is the expanded one with most of it hidden, so a shut row either shows the top of the form or shows nothing. Two views means a shut Shipping row can read "Standard, arrives Thursday" and an open one can be the address form. Redraw the figure with dart run tool/reveal_modes_figure.dart.

Instead of expandable. Semantics appears nowhere in its source, so ExpandableButton (lib/expandable.dart:751) hands a screen reader a bare InkWell with no button role and no expanded state. Four of its open issues are the ones people hit first: #8 asks for one panel open at a time (April 2019), #50 reports tapBodyToExpand not working (March 2020), #72 asks how to remove the header padding (September 2020), and #114 reports the example does not compile (October 2021, filed after the package's last release). The public API here is the same one, so the move costs an import line.

Reach for it when #

  • The collapsed and expanded states show different content, not the same content clipped.
  • A set of panels should behave as an accordion with one open at a time.
  • Panels need a correct button role and expanded state announced to a screen reader.

Skip it if a Material ExpansionTile already fits your design. It ships with the framework, it announces its own state changes through a live region (material/expansion_tile.dart:634), and one fewer dependency is worth more than the extras here.

Migration from expandable #

Change the import, and your existing panels behave the same.

// before
import 'package:expandable/expandable.dart';
// after
import 'package:expandable_plus/expandable_plus.dart';

The class names, fields, and defaults are the same. Your existing panels look and behave the way they did.

Install #

flutter pub add expandable_plus

Usage #

A basic panel #

ExpandablePanel(
  header: const Text('Details'),
  collapsed: const Text(
    'A short summary.',
    maxLines: 2,
    overflow: TextOverflow.ellipsis,
  ),
  expanded: const Text('The full text goes here.'),
)

An accordion group #

Pass one ExpandableGroupController to several panels. When one opens, the others close. Panels that are not in a group are unaffected.

final group = ExpandableGroupController();

Column(
  children: [
    ExpandablePanel(
      controller: ExpandableController(group: group),
      header: const Text('Section 1'),
      collapsed: const Text('Summary 1'),
      expanded: const Text('Body 1'),
    ),
    ExpandablePanel(
      controller: ExpandableController(group: group),
      header: const Text('Section 2'),
      collapsed: const Text('Summary 2'),
      expanded: const Text('Body 2'),
    ),
  ],
)

Pass ExpandableGroupController(allowAllCollapsed: false) to keep one section open at all times. Dispose the group when you are done with it, for example in your State.dispose.

Header padding #

headerPadding controls the space around the header. The default is EdgeInsets.zero, which matches expandable.

ExpandablePanel(
  theme: const ExpandableThemeData(headerPadding: EdgeInsets.all(16)),
  header: const Text('Details'),
  collapsed: const Text('Summary'),
  expanded: const Text('Body'),
)

Long lists: lazy #

A cross-fade keeps both children in the tree, and a collapsed panel still builds its expanded body. One panel never notices. Twenty do: put twenty collapsed ExpandablePanels in a ListView and fifteen expanded bodies are built on the first frame, one for every panel the viewport lays out. Measured in test/lazy_test.dart, which pins the number. Redraw the figure with dart run tool/lazy_cost_figure.dart.

Two lists of twenty collapsed panels side by side. In the left one, headed
"lazy: false", every laid-out row has a dashed red body behind it reading
"expanded body, built, never shown", and the count underneath is 15. In the
right one, headed "lazy: true", the rows are bare and the count is
0.

lazy: true holds a panel's body back until it first opens:

ExpandablePanel(
  lazy: true,
  header: const Text('Section'),
  collapsed: const Text('Summary'),
  // Your widget. `lazy` is what keeps it unbuilt until the panel opens,
  // which is the whole reason to reach for it.
  expanded: const HeavyBody(),
)

The first expand swaps the real child in, and it stays. Closing and reopening costs nothing and keeps whatever state the body was holding, because its initState runs once.

Off by default. Turning it on moves when a child's initState runs. That matters if the body has to be alive before anyone opens it, which makes this a decision rather than a default. Panels built through builder place both children themselves and are unaffected.

Accessibility #

The header is a real button to a screen reader, and it carries the panel's open state. A user hears "collapsed" or "expanded", and hears it change when they press it. That comes from ExpandableButton, which every header and header icon goes through. Panels and accordion groups get it without any setup:

ExpandablePanel(
  header: Text('Details'),   // announced as a button, expanded or collapsed
  collapsed: Text('Summary'),
  expanded: Text('Everything'),
)

Nothing needs to be passed for this. It tracks the controller, and expanding a panel from code updates the announcement too.

What's fixed #

  • Open one panel at a time: #8
  • Remove the padding around the header: #72
  • tapBodyToExpand and tapBodyToCollapse not working: #50
  • Example not compiling: #114
  • No screen-reader support: the header exposed no button role and no expanded state, leaving the control unusable with assistive technology

Credits #

Based on expandable by Alexander Ryzhov (MIT). Original repository: https://github.com/aryzhov/flutter-expandable

License #

MIT. See LICENSE.

0
likes
160
points
723
downloads
screenshot

Documentation

API reference

Publisher

verified publisherdeveloperyusuf.com

Weekly Downloads

Expandable and collapsible panels with accordion groups for Flutter. A maintained, drop-in successor to the expandable package; adds header padding, fixes body-tap toggling.

Repository (GitHub)
View/report issues

Topics

#expandable #accordion #collapsible #widget #ui

License

MIT (license)

Dependencies

flutter

More

Packages that depend on expandable_plus