Toggling a class on a Tailwind/Alpine.js accordion

Viewed 1117

I've created an accordion with TailwindCSS and Alpine.js which works fine except that I also want to change the icon in the button that expands the content when it's clicked.

This is what I have:

<div x-data="{selected:null,open:true}">

  <dl class="faqs mx-auto max-w-2xl">
    <dt>
      <span class="faq-q">Question</span>
      <button
        type="button"
        class="faq-toggle"
        @click="selected !== 1 ? selected = 1 : selected = null, open = open"
        :class="{ 'faq-open': open, 'faq-close': !(open) }"
      >
        <span>+</span>
        <span class="hidden">-</span>
      </button>
    </dt>
    <dd
      class="faq-a overflow-hidden transition-all max-h-0 duration-700"
      style="" x-ref="container1" x-bind:style="selected == 1 ? 'max-height: ' + $refs.container1.scrollHeight + 'px' : ''"
    >
      <div class="inner">
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iure rerum in tempore sit ducimus doloribus quod commodi eligendi ipsam porro non fugiat nisi eaque delectus harum aspernatur recusandae incidunt quasi.
      </div>
    </dd>
  </dl>
</div>

and a link to a CodePen.

What I want to do is toggle the class of the button from faq-open to faq-close when the button is clicked. Although I may actually need to toggle a class on the parent dt too.

At the moment, the accordion expands when you click on the button, but the class doesn't change.

2 Answers

The problem is on this line

@click="selected !== 1 ? selected = 1 : selected = null, open = open"

You never change value of open, it is always the value at initialization, and that is open: true.

You need to switch it:

@click="selected !== 1 ? selected = 1 : selected = null, open = !open"

By the way, you don't need extra variable selected to control the hidden text, just one open variable is enough. Something like this:

<div x-data="{open: true}">
    <dl class="faqs mx-auto max-w-2xl">
      <dt>
        <span class="faq-q">Question</span>
        <button
          type="button"
          class="faq-toggle"
          @click="open = !open"
          :class="open ? 'faq-open' : 'faq-close'"
        >
          <span :class="open ? '' : 'hidden'">+</span>
          <span :class="open ? 'hidden' : ''">-</span>
        </button>
      </dt>
      <dd
        class="faq-a overflow-hidden transition-all max-h-0 duration-700"
        style="" x-ref="container1" x-bind:style="open ? 'max-height: ' + $refs.container1.scrollHeight + 'px' : ''"
      >
        <div class="inner">
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iure rerum in tempore sit ducimus doloribus quod commodi eligendi ipsam porro non fugiat nisi eaque delectus harum aspernatur recusandae incidunt quasi.
        </div>
      </dd>
    </dl>
import 'twin.macro';
import React, { useRef, useState } from 'react';
import { BiPlus, BiMinus } from 'react-icons/bi';

const Accordion = ({ title, children }) => {
  const [active, setActive] = useState(false);
  const [height, setHeight] = useState('0px');

  const contentSpace = useRef(null);
  function toggleAccordion() {
    setActive(!active);
    setHeight(active ? '0px' : `${contentSpace.current?.scrollHeight}px`);
  }

  return (
    <div tw="flex flex-col outline-none" role="button" tabIndex={0}>
      <div
        tw="flex flex-row items-center justify-between py-4 border-b border-t border-black "
        onClick={toggleAccordion}>
        <h1 tw="cursor-pointer text-base font-bold">{title}</h1>
        {active ? <BiMinus /> : <BiPlus />}
      </div>
      <div
        ref={contentSpace}
        style={{ maxHeight: `${height}` }}
        tw="overflow-auto overflow-y-hidden duration-700 ease-in-out">
        <p tw="my-4">{children}</p>
      </div>
    </div>
  );
};
export default Accordion;
Related