Made in Framer: Accordion List

Welcome to Made in Framer, a series of articles exploring common design problems (and solutions) in Framer.

Like any good guide, the finished product first.

In this article, I’ll go through building an accordion list, also known as a list of collapsibles or a list with expandable content. It’s a deceptively simple element, with behavior that seems obvious because it follows the rules of real time and space — where, for example, two things cannot occupy the same space and an expanding object will push others out of its way.

As with all new challenges in Framer, we’ll have to look closer to see exactly what is going on, translate what we see into rules, and represent those rules in our code. If you don’t already have Framer, download their 14 day free trial to follow along.

Let’s get started.

Isolating our example

For the sake of this guide, I’ll use an example with the following constraints:

  • our accordion list is vertical
  • each list layer begins in a collapsed state
  • tapping on a collapsed list layer will expand it
  • layers below an expanding list layer will move down
  • tapping on an expanded list item will collapse it
  • layers below a collapsing list layer will move up
  • more than one list layer can be expanded at a time
  • all list layers have the same height when collapsed
  • all list layers have the same height when expanded

If your use case differs, take note of what’s different. For example, your list may be horizontal, in which case layers to the right of your expanding layer would have to move to the right; or your items may each have different heights when expanded. I’ll address some of these cases at the end.

Setting up our Layers

Before we get into the functionality, let’s lay out our layers. Nothing surprising here: a scroll component and loop that creates twelve layers, each a child of the scroll component’s content, spaced vertically. Since we’re going to be animating these layers, I’ve given them an animationOptions property.

Our list of layers, created with a loop.

The most important thing here is that all of our list layers share the same parent. As we’ll see below, we’ll need an array that contains all of our list layers, and we’ll be using our layers’ family relationships to generate this array.

Expanding

Once we have our layers in place, our next step is to start building a function to handle our expand and collapse actions. We’ll start by developing the expand functionality, then move on to collapse.

“Expand” is somewhat of an abstract concept, so let’s unpack it to help us design our function.

  • By expand, we mean that when a layer is tapped, its height increases by a certain amount, and the layers below it move down by increasing their y values by that same amount.

Now let’s represent that in code.

Expansion is in: effective, dynamic… and irreversible.

Take a look at what’s new, then let’s review our changes.


We’ve created a function toggleExpand and designed it to accept two arguments. The first argument,layer, is the layer that we’ll be expanding. The second,distance, is the number of points to expand it.

The function begins by animating the layer’s height to its current height plus the value of distance. Next, it loops over an array of the layer’s siblings. For each sibling, the function checks whether the sibling’s y value is greater than the layer’s y value, which it would be if the sibling is below our expanding layer. If this check returns true, our function animates the sibling’s y value to its current y value plus distance.

To trigger this event, we’ve gone back into the loop that creates our list layers and given each list layer a tap event. When a list layer is tapped, it runs our toggleExpand function, passing itself as the layer argument and 96 as the distance argument. A word on that miukumauku: in event functions, sometimes called callbacks, the keyword this (or its alias @) will refer to the layer that emitted the event — in our case, the list layer that was tapped.

The code works! Our list layers expand when we tap on them. As they grow larger, the layers below them move down. The problem is, of course, that our current code only allows a list layer to expand; and, because our list layers have no way of knowing whether they are already expanded, tapping a list layer multiple times will continue expanding it.

Expanding and Collapsing

When we tap on a list layer, we want one of two things to happen: if the layer is not expanded, we want it to expand; or, if it is expanded, we want it to collapse.

We have two abstract concepts there that we’ll need to unpack: “expanded” and “collapse”. We could also include “collapsed,” however it is the same as saying “not expanded”, so we won’t define it on its own.

  • By “expanded”, we mean whether a layer has been tapped and is taller than it was before.
  • By “collapse”, we mean that when an expanded layer is tapped, its height decreases by a certain amount, and the layers below it move up by decreasing their y values by that same amount. When an expanded layer collapses, it is no longer expanded.

And we can extend our definition of “expand” with “expanded” in mind.

  • By “expand”, we mean that when a not expanded layer is tapped, its height increases by a certain amount, and the layers below it move down by increasing their y values by that same amount. When a not expanded layer expands, it is expanded.

A little pedantic? Sure, but we can represent these rules in code.

Four more lines solves are all we need.

Take another look at what’s new, then let’s review our changes.


First, we’ve gone back into the loop we’re using to create our list layers, giving each list layer a property called expanded. Our layers are born collapsed — or, to stick to our terms, not expanded — so we’ll set this value to false.

Next, we’ve added a somewhat verbose line to our toggleExpand function. This redefines the value of the distance argument based on the layer argument’s expanded property. If layer.expanded is true, then distance becomes negative. If not, it stays positive. Because the only difference between an expand action and a collapse action is whether the distance is positive or negative, the rest of the code can be left as is. If the value of distance is positive, the layer will grow in height and the layers beneath it will move down. If it is negative, the layer will shrink in height and the layers beneath it will move up.

Last, we’ve added a line that will reverse the value of the expanding/collapsing layer’s expanded property. If expanded was false when the function began, then the function will have just expanded the layer and will change expanded from false to true. The next time we tap the layer, its expanded value will be true, and as a result the function will collapse the layer. Likewise, if the layer was expanded before, it will expand again next time we tap it.

And, since we will have modified the height of our layers, we should update our scroll component once our animations have ended. There are several options for doing this with callbacks, but we’ll use a short delay instead. Scroll components clip their content, so if we were to skip this step (as I did when first writing this guide), we would start losing the bottom of our list as more layers are expanded.

Conclusion

That covers the basics. Our list layers expand, contract, and adjust to the positions of other list layers. Job well done, but if this were a real project, we wouldn’t stop there.

With the basic functionality in hand, we might go on to create clipped content that is revealed on expansion, or give our layers specific buttons for handling expand/collapse toggles. We might add code that would limit our list to only one expanded list layer at a time, or create a control for collapsing all list layers. We could also write our code to allow for different expanded heights.

And, if it were me, I would definitely go back and rename all of these layers to ‘.’, so that they wouldn't flood my list of layers.

All this and more!

Feel free to download, read and expand on these projects, both the simple project from our example and the more complex one above. If you have any tips or questions, or if you have an idea for a future article, let me know in the comments below.