For

2023.9.16

Radix UIとTailwind CSS v3.2で追加されたdata属性に対するスタイル適用でAccordionを作る

概要

これまでフォームを構成する要素や機能、アコーディオンやモーダルなどの機能についてはMaterial-UIを使用することが多かったですが、スタイルを上書きするのが面倒な場合があったり、深いところまでカスタムしようとするとプロパティに型が提供されておらずany型になっていたりして、ストレスを感じながらカスタマイズしていました(と言いつつ、後日Material-UIのカスタマイズについて記事を書く予定ではあります)。
そこで、機能だけ提供してくれるというRadix UIを試しつつ、今回はTailwind CSS v3.2で追加されたdata属性に対するスタイルを適用する方法を使って、アコーディオンを実装してみたいと思います。

主なパッケージ

・next 13.4.19
・@radix-ui/themes 1.1.2
・@radix-ui/react-accordion 1.1.2
・tailwindcss 3.3.3

実装

Accordionの確認

まずはとりあえず Radix-UI Accordion - Installation にあるパーツをすべて貼り付けて、適当に文字列を入れて動きを確認してみます。

tsx_____page.tsx_____'use client';

import { Card } from '@/components/Common/Card';
import { CardHeading } from '@/components/Common/CardHeading';
import * as Accordion from '@radix-ui/react-accordion';

export default function Playground() {
  return (
    <Card>
      <CardHeading text={'Accordion'} />
      <Accordion.Root type="multiple" className="mt-24">
        <Accordion.Item value="1">
          <Accordion.Header>
            <Accordion.Trigger>
              <p>Accordion.Item / value:1 / Accordion.Trigger</p>
            </Accordion.Trigger>
          </Accordion.Header>
          <Accordion.Content>
            <div>
              <p>Accordion.Item / value:1 / Accordion.Content</p>
            </div>
          </Accordion.Content>
        </Accordion.Item>
      </Accordion.Root>
    </Card>
  );
}


今回とりあえず動かしたかったためページコンポーネントで 'use client' してるのはご愛嬌です。
App Routerを否定してしまっているようですがこのブログはApp Routerを利用しているので悪しからず。

CardCardHeading は私がTailwind CSSでスタイリングしただけのdiv要素なので、中の Accordion を見てください。
ただのこの状態で動かしてみます。



なるほど、本当になにもスタイルがついていなくてとても気持ちがいいですね。

data属性と要素の高さ

Accordionを開いたときにDOM要素がどうなっているか確認してみます。



Accordion.Contentの要素を見てみると data-state という属性が closed から open に変わっていることがわかります。
また、Accordionを自分で実装しようとすると高さを取得しないといけないことがわかると思いますが、それはRadix UIが計算して変数 --radix-accordion-content-height にセットしてくれているようです。

Tailwind CSSのアニメーション定義

今回はなんとなくアニメーションを使ってスタイルを定義したいと思います。
tailwind.config.tsextend の中に次のような keyframesanimation を追加します。

TypeScript_____tailwind.config.ts_____// 省略
  theme: {
    extend: {
      keyframes: {
        'slide-down': {
          '0%': { height: '0px' },
          '100%': { height: 'var(--radix-accordion-content-height)' },
        },
        'slide-up': {
          '0%': { height: 'var(--radix-accordion-content-height)' },
          '100%': { height: '0px' },
        },
      },
      animation: {
        'slide-down': 'slide-down 0.3s ease',
        'slide-up': 'slide-up 0.3s ease',
      },
// 省略


先ほど確認できたRadix UIがセットしてくれた高さの変数をkeyframesの定義で利用します。
Accordionが開くときはその高さを100%のときの値とし、閉じるときはその逆です。
また、そのkeyframesを利用できるようにするためanimationプロパティにもアニメーションの定義を追加しておきます。

Accordionにクラスを付与

Tailwind CSS v3.2で追加されたdata属性の状態に応じてスタイルを適用できる機能を利用していきます。
詳しくは公式ドキュメント Tailwind CSS Documentation - Data attributes を参照ください。
Radix UIでは data-state というdata属性の値を変更してくれていたのでそれを利用しつつ、好みのクラスも追加して以下のようにしてみました。

tsx_____page.tsx_____'use client';

import { Card } from '@/components/Common/Card';
import { CardHeading } from '@/components/Common/CardHeading';
import * as Accordion from '@radix-ui/react-accordion';

export default function Playground() {
  return (
    <Card>
      <CardHeading text={'Accordion'} />
      <Accordion.Root type="multiple" className="mt-24">
        <Accordion.Item value="1" className="rounded-8 overflow-hidden">
          <Accordion.Header className="bg-gray-100">
            <Accordion.Trigger className="w-100p p-24 text-left">
              <p>Accordion.Item / value:1 / Accordion.Trigger</p>
            </Accordion.Trigger>
          </Accordion.Header>
          <Accordion.Content className="overflow-hidden data-[state=open]:animate-slide-down data-[state=closed]:animate-slide-up">
            <div className="bg-gray-200 p-24">
              <p>Accordion.Item / value:1 / Accordion.Content</p>
            </div>
          </Accordion.Content>
        </Accordion.Item>
      </Accordion.Root>
    </Card>
  );
}


data属性に対するスタイル適用のアプローチは data-[state=open]:data-[state=closed]: の部分です。
それに続く部分で data-state の状態に応じたスタイルを適用させています。
また、今回は tailwind.config.ts でkeyframesとanimationをextendで定義したので上記のようにクラスを付与します。
その他適当なマージンや背景色もつけてみたので、これで動かしてみます。



いい感じになりました!

まとめ

Radix UIは機能だけ、Tailwind CSSはスタイル適用だけという責務の分担がうまく機能したと思います。
Radix UIが更新するdata属性の値に応じて、Tailwind CSS v3.2から利用できるようになったdata属性へのアプローチが噛み合いました。
これなら自由度が高くてストレスも少なく開発できそうです。
機能の充実性ではまだMaterial-UIに劣るところもあるかもしれませんが、少なくとも現状ではRadix UIとTailwind CSSで様々なコンポーネントがどこまでカスタマイズできるか試してみたくはなりました。