You must have seen this interaction in many of the apps.

source —

What does it do?

Expansion panel list is a list that shows its children using expansion animation on click of item.

Let’s see the basic structure for the below output

  expansionCallback: (int index, bool isExpanded) {},
  children: [
      headerBuilder: (BuildContext context, bool isExpanded) {
        return ListTile(
          title: Text('Item 1'),
      body: ListTile(
        title: Text('Item 1 child'),
        subtitle: Text('Details goes here'),
      isExpanded: true,
      headerBuilder: (BuildContext context, bool isExpanded) {
        return ListTile(
          title: Text('Item 2'),
      body: ListTile(
        title: Text('Item 2 child'),
        subtitle: Text('Details goes here'),
      isExpanded: false,

Understand it’s properties

➊ expansionCallBack:

This gets called whenever the expand/collapsed button is pressed on any of the items inside the list.

It gives us two things.

  1. Index of the clicked item
  2. Whether to expand or collapsed the item clicked.

➋ children:

ExpansionPanel widget is used as a child for the ExpansionPanelList. This widget has got below properties.

  1. headerBuilder: Used to show the header of the item.
  2. body: Used to show the details of the item when expanded.
  3. isExpanded: This is very important as it decides whether to expand/collapsed the item or not. Defaults to false.
  4. canTapOnHeader: Be default, You have to click on ^ icon to expand but you can pass true to this property to also make header of the item clickable.

➌ animationDuration:

Used to define the duration in which the expand and collapsed animation should complete. It defaults to 200ms.

However, it is recommended to keep it to the original you can still change it to anything like this.

animationDuration: Duration(milliseconds: 300),

Let’s make it dynamic

The basic code above won’t actually expand/collapse. That was just a starter code to make things clear before we move on.

So how do we actually make it work? Here are the steps.

➊ Create a class that will hold the data for the item.

class Item {
    this.isExpanded = false,

  String expandedValue;
  String headerValue;
  bool isExpanded;

bool isExpanded will be used to determine whether the item needs to expand or not.

➋ Prepare a list of items.

List<Item> generateItems(int numberOfItems) {
  return List.generate(numberOfItems, (int index) {
    return Item(
      headerValue: 'Book $index',
      expandedValue: 'Details for Book $index goes here',
List<Item> _books = generateItems(8);

➌ Map each item in the list to ExpansionPanel because we are going to use it as a child for the ExpansionPanelList and not the Item itself.

children:<ExpansionPanel>((Item item) {
  return ExpansionPanel();

➍ On receiving expansionCallback change the isExpanded parameter of the item inside the list of books and rebuild a widget.

expansionCallback: (int index, bool isExpanded) {
  setState(() {
    _books[index].isExpanded = !isExpanded;

Click on RunPen to read and play with the code

Thanks for reading. If you found this article to be helpful please share it with your friends.


If you want to have only one item to be expanded at a time and close all other panels on the list then try this.

For more about programming, follow me and Aubergine Solutions, so you’ll get notified when we write new posts.

Little about me

Hi, Myself Pinkesh Darji and I love to solve a real-world problem using Technology that improves user’s life on a major scale.