Last week, while working on a map for the Mont-Royal Park, I was inspired to build a recursive Vue.js accordion component. In this short tutorial, I'll cover how I built it.

Source Code

You can view the source code on Github

A basic recursive accordion

A recursive component is just an element that can call itself many times. Let's build a basic example to start:

RecursiveAccordion.vue

<template>
  <div class="recursive-accordion">
    <h1>{{ node.title }}</h1>
    <RecursiveAccordion
      v-for="(child, childIndex) in node.children"
      :key="childIndex"
      :node="child" />
  </div>
</template>

<script>
export default {
  name: 'RecursiveAccordion',
  props: ['node']
}
</script>

Two properties are required to make this work:

  • name so the component can call itself
  • node which holds data for the current node with its children

All that's left to do is to call our component in our App and pass it some data:

App.vue

<template>
  <div id="app">
    <RecursiveAccordion :node="tree" />
  </div>
</template>

<script>
import RecursiveAccordion from './components/RecursiveAccordion'

export default {
  name: 'app',
  data () {
    return {
      tree: {
        title: 'Root Node',
        children: [
          {
            title: 'Child Node #1',
            children: [
              {
                title: 'Deep Child Node'
              }
            ]
          },
          {
            title: 'Child Node #2'
          }
        ]
      }
    }
  },
  components: {
    RecursiveAccordion
  }
}
</script>

Source Code

You can view the source code for this step on Github (step 1)

Adding depth

In the previous step, we built our tree but there's no visual hierarchy; all the parents and children look alike. Let's change that by indenting them according to their depth.

First, we'll add a depth property and specify a default value:

RecursiveAccordion.vue

props: {
  node: {
    type: Object
  },
  depth: {
    type: Number,
    default: 0
  }
}

Then, let's increment the children's depth by passing :depth="depth + 1":

RecursiveAccordion.vue

<RecursiveAccordion
  v-for="(child, childIndex) in node.children" :key="childIndex"
  :node="child"
  :depth="depth + 1"
/>

Finally, let's add some padding to make the different depths visually distinct:

RecursiveAccordion.vue

<div class="recursive-accordion" :style="{paddingLeft: depth * 16 + 'px'}">

Source Code

You can view the source code for this step on Github (step 2)

Collapsible accordions

The end goal is to build nested accordions, so let's make them collapsible. All we need to do is add an isOpen state to our component to track which accordions are open.

First let's add isOpen to our component's state:

RecursiveAccordion.vue

data () {
  return {
    isOpen: false
  }
}

Then we'll add a method for toggling the isOpen state:

RecursiveAccordion.vue

methods: {
  clickHandler (event) {
    this.isOpen = !this.isOpen
  }
}

Finally, we'll add a click handler on the accordions so we can expand or collapse them:

RecursiveAccordion.vue

<h1 @click="clickHandler">{{ node.title }}</h1>

Note: We add the clickHandler to the <h1> instead of the whole .recursive-accordion because clicking on the children would otherwise toggle the parents.

Source Code

You can view the source code for this step on Github (step 3)

Final Touches

With a little extra work, you can get to a much better looking accordion. To keep things short, I won't cover all the style changes here but you have access to the full source code. However, I will leave you with a quick tip. When styling recursive components, often, you'll want to use the child combinator CSS selector, aka >. For example, when styling a node at depth 0, we don't necessarily want the styles to apply to deeper nodes. In those cases, we can use the child combinator like this:

RecursiveAccordion.vue

.recursive-accordion--depth-0 > .recursive-accordion__item {
  background: white;
  color: #4d4d4f;
}

Source Code

You can view the source code for the final result on Github