Creating an Accordion Design: Part I

30m

Intermediate

Getting started

We’ll be making this project with Framer. If you don’t have it already, download a free 14-day trial.

Creating an accordion design

Creating realistic user experiences requires interactions and animations. In other design tools, you often end up creating visual representations of these actions. With Framer, you can bring them to life.

We’ll take a deep dive into different common interactions and show you how to create these yourself using Framer. One common interaction is the accordion design. These often feature card-like elements you can toggle open and closed, as shown in the example below.

In this first part of the series, we’ll go over:

  • Creating a design component
  • Using a design component in a code component
  • Making a card that toggles between two (visual) states

The second part will include turning one card into multiple cards and adding a custom animation, both using code. We’ll also add advanced logic, which will ensure that only one card is active at a time. The final part focuses on using data from a JSON file in our cards, so it can even pull data from (live) APIs.

When you’re finished with part one, you’ll have created something like this:

Some notes and starting words

This guide aims at anyone with a beginner-level understanding of Framer and Javascript/React. If at times the syntax seems confusing, don’t stress about it.

Focus on what we are trying to do instead of curly braces and brackets—try to not let new code intimidate you. Trial and error are key, and over time I promise it will start to make sense.

  • Even though you’ll be able to take the code from the example file at the bottom and the snippets, I encourage you to type it out yourself. Rewriting will make you understand and remember better.
  • If you’d prefer to achieve this effect with code overrides, I highly recommend Steve Ruiz’s lesson.

Laying the groundwork

Let’s get started. In the example below, we’ve created a card design by using multiple frames and text.

Design your card how you want it to look and press command + K to turn it into a design component. The purple outline will indicate that your element is now a component.

Note: make sure you’ve given your design component a unique name. You can do this by right-clicking the layer from the layers panel.

Let’s also create a code component, as this is where all the magic will happen. Go to the Code section and select the button that says New.

You’ll now end up in the code editor with our newly created code component file. Some preloaded code is already in there to help you in the right direction, but you can delete this as we’ll be starting fresh.

We want to use React in this file, so let’s start by importing it into the file.

import * as React from "react"
COPY

On top of that, we want to use the design component we’ve created earlier, so we import it from the canvas folder under the path ./canvas. Remember to use the unique name that you’ve given your design component earlier.

Protip: you can use anything from the canvas in a code component, you just need to turn it into a design component first.

import * as React from "react"
import { Card } from "./canvas"
COPY

Now that we have everything we need, let’s create the outline of our code component. In React, we define components using functions.

If you’d like to learn more about the syntax, I recommend reading the Framer Guide to React.

import * as React from "react"
import { Card } from "./canvas"
export function Accordion() {
// this is where we'll create the content of our component
}
COPY

Rendering your card on the canvas

Next, we want our Accordion component to show the Card component that we’ve brought in from the canvas.

How a component "looks" depends on what its function returns, so let’s tell the function to return our card by adding it to the function body.

import * as React from "react"
import { Card } from "./canvas"
export function Accordion() {
return (
<Card />
)
}
COPY

In the code above, we’ve enclosed the name of our design component in a tag by writing <Card/>. We call this notation JSX, and you can think of it as a special form of HTML to use in React. You’ll learn more about JSX in this article and in the Framer Guide to React.

If you now head back to the canvas, you’ll find your code component under the components panel named Accordion. It will look the same as your design component, as so far we’ve only imported the design component and told the function to return it with nothing else added.

Preparing the card

Using variants

We’re doing interactive design, so we do want something to happen. Namely, we want our card to know to switch back to its previous closed state when you tap it again. Lucky for us, React was built to handle states.

export function Accordion() {
const cardVariants = {
open: {
height: "174px",
},
closed: {
height: "98px",
},
}
return (
<Card variants={cardVariants} />
}
COPY

In the code above, I’m creating a variable called cardVariants that will hold our component’s variants. Variants are a handy way allowing us to assign different (visual) properties to multiple distinctly different states.

To be specific, the open state will have a height of 174px, while our closed state will be 98px. We’ll be able to refer to open and closed as our styling later on.

We also need to make sure that our component is aware of our variants, which is why we added the variants property to our <Card/> component which points to the variable cardVariants. We’re essentially saying check out the two variants stored under variable cardVariants if you want to know what styling and behavior to apply to the card.

But which variant should apply initially to our component, and how do we make sure it animates to a different variant?

Using states with Hooks

To answer this, we’ll first need to find a way to make the card interactive as we need to tell the card to change between our variant styles when we tap on it. One way to do this is to tell React to remember the state the component is currently in, and make changes accordingly. We can do this with the useState hook.

Hooks are an interesting concept in React. In the Framer Guide to React, Koen explained it in the following way:

Hooks are a hip name for things you can do in a component. The most common one that you will use is `useState`. It allows the component to remember some value, and update when it changes.

We declare them under a variable const— as with our variants — only it’ll look a tad different:

const [isOpen, setIsOpen] = React.useState(null)
COPY

Which makes our component look something like this:

import * as React from "react"
import { Card } from "./canvas"
export function Accordion() {
const [isOpen, setIsOpen] = React.useState(null)
const cardVariants = {
open: {
height: "174px",
},
closed: {
height: "98px",
},
}
return (
<Card variants={cardVariants} />
)
}
COPY

Remember how we imported the React library at the start? Here we’re using the useState hook directly from the React library by saying React.useState

Within our component function Accordion, we then declare a new state variable called isOpen which will preserve a certain state of your component. Our useState hook also takes in a function that we call setIsOpen. When we call this function, it allows us to set new values to the state.

Within the brackets of React.useState, we can pass along properties (or props) that our state of isOpen may have. In this example, we want our card to close by default. We’ll decide that later on though, so we can just pass a value of null to our first starting state. This explicitly says we did not decide yet on the starting state.

Hooks may be tricky to grasp and that’s totally fine. The syntax may seem a bit odd, but it will definitely become more clear the more you practice it. Explore the web and read more about Hooks and soon you’ll reach that a-ha! moment.

For now, all you need to know is that we just told our component to keep in mind that isOpen will be able to have different states, that we can change this by using setIsOpen, and that our value for isOpen is null in our starting state.

Let’s animate

So far we’ve been working on the logic we need for our card’s state. It’s time to make some stuff move.

Within the component’s function body we’ll be determining its behavior. So what do we want our card to do when we tap it, and what do we need for that?

  • onTap — When we tap the card, we want the state to change to (in)active
  • Animate — The visual/behavioral properties we tell the card to animate to
  • Initial — The starting state of our component
<Card
variants={cardVariants}
onTap={() => {
isOpen ? setIsOpen(false) : setIsOpen(true)
}}
animate={isOpen ? "open" : "closed"}
initial={false}
/>
COPY

Don’t worry about all the unfamiliar code elements for now, let’s break down one by one what we’re doing here:

onTap

onTap={() => {
isOpen ? setIsOpen(false) : setIsOpen(true)
}}
COPY

With the code above we say that if we tap our card onTap=, we want to run another function () =>. We consider this way of writing a function a shorthand notation.

In this function, we are evaluating the following question:

Look at what the current value is for isOpen — if it’s true ? then set the state to false. Otherwise : set the state to true.

The usage of ? and : are an indication that we use a conditional operator to keep it short and concise.

In the end, with each tap, we essentially look at whether the card is currently open. If so, we close it, and if it’s closed already we instead open it up.

Animate

initial={false}
animate={isOpen ? "open" : "closed"}
COPY

The animate property tells our code to “animate to the following values”. In our accordion design, the animate property should depend on the state of the card.

With the above lines of code, we tell our card that when it shows, we want it to anime to the visual and behavioral properties stored in our variants. Which variant to use is then based on whether React evaluated isOpen to be true or false. We similarly do this as how we evaluated our onTap function.

When isOpen is true we assign the styling that we saved under variant open. Otherwise, we want it to use the styling under variant closed.

Initial

We use the initial property to dictate how we want our card to behave when it first renders. If we wouldn’t include this, our card would render in our code component and animate immediately to the closed state due to the animate property. We don’t want this, as the card should start out closed.

We write initial={false} to instead tell our component to start in the first state that it would otherwise have animated to. Read the API documentation to learn more about this.

Finished work

Congrats, we’ve created a card that will open and collapse by tapping it, and it will keep track of its state allowing us to go back and forth between open and closed. A quick peek at our current code editor gives us the following:

import * as React from "react"
import { Card } from "./canvas"
export function Accordion() {
const [isOpen, setIsOpen] = React.useState(null)
const cardVariants = {
open: {
height: "174px",
},
closed: {
height: "98px",
},
}
return (
<Card
variants={cardVariants}
onTap={() => {
isOpen ? setIsOpen(false) : setIsOpen(true)
}}
animate={isOpen ? "open" : "closed"}
initial={false}
/>
)
}
COPY

Review

Download the finished project for part I ›

We did not tell Framer to use any custom animations, so right now it animates in a snappy quick motion which is the default animation for a frame. With Part II, we’ll customize this a bit more.

Also, we’re digging into how we can turn one card into multiple cards with more advanced logic. Tapping a new card will then both close the previous one ánd open the newly tapped card. Interestingly enough, this will be only one line of code.

Got stuck before the finish line somewhere? Feel free to reach out by sending me a tweet. If you’d rather start a discussion head on over to our communities at Spectrum or Slack. Of course, you can also contact support directly.