開示

折りたたみ可能なアコーディオンパネルなど、コンテンツの表示と非表示を切り替えるカスタムUIを構築するためのシンプルでアクセシビリティの高い基盤です。

インストール

開始するには、npm 経由で Headless UI をインストールします。

npm install @headlessui/react

基本例

開示は、DisclosureDisclosureButtonDisclosurePanel コンポーネントを使用して構築されています。

ボタンをクリックすると、パネルが自動的に開閉され、すべてのコンポーネントは、aria-expandedaria-controls のような適切な aria-* 関連属性を受け取ります。

import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'

function Example() {
  return (
    <Disclosure>
      <DisclosureButton className="py-2">Is team pricing available?</DisclosureButton>
      <DisclosurePanel className="text-gray-500">
        Yes! You can purchase a license that you can share with your entire team.
      </DisclosurePanel>
    </Disclosure>
  )
}

スタイル

Headless UI は、どのリストボックスオプションが現在選択されているか、ポップオーバーが開いているか閉じているか、開示内のどのアイテムが現在キーボードでフォーカスされているかなど、各コンポーネントに関する多くの状態を追跡します。

しかし、コンポーネントはヘッドレスで、すぐに使える状態では完全にスタイルが適用されていないため、各状態に必要なスタイルを自分で提供するまで、UIでこの情報を見ることはできません。

データ属性の使用

Headless UI コンポーネントのさまざまな状態をスタイリングする最も簡単な方法は、各コンポーネントが公開するdata-*属性を使用することです。

たとえば、DisclosureButtonコンポーネントはdata-open属性を公開しており、これにより開示が現在開いているかどうかがわかります。

<!-- Rendered `Disclosure` -->
<button data-open>Do you offer technical support?</button>
<div data-open>No</div>

これらのデータ属性の存在に基づいてスタイルを条件付きで適用するには、CSS 属性セレクター を使用します。Tailwind CSS を使用している場合は、データ属性修飾子 を使用すると簡単です。

import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'

function Example() {
  return (
    <Disclosure>
      <DisclosureButton className="group flex items-center gap-2">
        Do you offer technical support?
<ChevronDownIcon className="w-5 group-data-[open]:rotate-180" />
</DisclosureButton> <DisclosurePanel>No</DisclosurePanel> </Disclosure> ) }

使用可能なすべてのデータ属性のリストについては、コンポーネントAPIを参照してください。

レンダープロップの使用

各コンポーネントは、レンダープロップを介して現在の状態に関する情報を公開しており、これを使用して、異なるスタイルを条件付きで適用したり、異なるコンテンツをレンダリングしたりできます。

たとえば、DisclosureButtonコンポーネントはopen状態を公開しており、これにより開示が現在開いているかどうかがわかります。

import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import clsx from 'clsx'

function Example() {
  return (
    <Disclosure>
{({ open }) => (
<> <DisclosureButton className="flex items-center gap-2"> Do you offer technical support?
<ChevronDownIcon className={clsx('w-5', open && 'rotate-180')} />
</DisclosureButton> <DisclosurePanel>No</DisclosurePanel> </>
)}
</Disclosure> ) }

使用可能なすべてのレンダープロップのリストについては、コンポーネントAPIを参照してください。

トランジションの追加

開示パネルの開閉をアニメーション化するには、DisclosurePanelコンポーネントにtransitionプロップを追加し、CSSを使用してトランジションのさまざまな段階をスタイル設定します。

import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'

function Example() {
  return (
    <Disclosure as="div" className="w-full max-w-md">
      <DisclosureButton className="w-full border-b pb-2 text-left">Is team pricing available?</DisclosureButton>
      <div className="overflow-hidden py-2">
        <DisclosurePanel
transition
className="origin-top transition duration-200 ease-out data-[closed]:-translate-y-6 data-[closed]:opacity-0"
>
Yes! You can purchase a license that you can share with your entire team. </DisclosurePanel> </div> </Disclosure> ) }

内部的には、transitionプロップはTransitionコンポーネントとまったく同じ方法で実装されています。詳細については、トランジションドキュメントを参照してください。

Framer Motion を使用したアニメーション

Headless UI は、Framer MotionReact SpringなどのReactエコシステムの他のアニメーションライブラリとも連携します。これらのライブラリに状態を公開するだけです。

たとえば、Framer Motion を使用してメニューをアニメーション化するには、DisclosurePanelコンポーネントにstaticプロップを追加し、openレンダープロップに基づいて条件付きでレンダリングします。

import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
import { AnimatePresence, easeOut, motion } from 'framer-motion'
import { Fragment } from 'react'

function Example() {
  return (
<Disclosure as="div" className="w-full max-w-md">
{({ open }) => ( <> <DisclosureButton className="w-full border-b pb-2 text-left">Is team pricing available?</DisclosureButton> <div className="overflow-hidden py-2"> <AnimatePresence> {open && (
<DisclosurePanel static as={Fragment}>
<motion.div initial={{ opacity: 0, y: -24 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -24 }} transition={{ duration: 0.2, ease: easeOut }} className="origin-top" > Yes! You can purchase a license that you can share with your entire team. </motion.div> </DisclosurePanel> )} </AnimatePresence> </div>
</>
)} </Disclosure> ) }

開示を手動で閉じる

パネルの子をクリックしたときに開示を手動で閉じたい場合は、その子をCloseButtonとしてレンダリングします。asプロップを使用して、レンダリングされる要素をカスタマイズできます。

import { CloseButton, Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
import MyLink from './MyLink'

function Example() {
  return (
    <Disclosure>
      <DisclosureButton>Open mobile menu</DisclosureButton>
      <DisclosurePanel>
<CloseButton as={MyLink} href="/home">
Home
</CloseButton>
</DisclosurePanel> </Disclosure> ) }

これは、次のページに移動するときに開示を閉じたいリンクを含むモバイルメニューなどのために開示を使用する場合に特に役立ちます。

DisclosureDisclosurePanelは、非同期アクションの実行後など、パネルを命令的に閉じることができるcloseレンダープロップも公開しています。

import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'

function Example() {
  return (
    <Disclosure>
      <DisclosureButton>Terms</DisclosureButton>
      <DisclosurePanel>
{({ close }) => (
<button
onClick={async () => {
await fetch('/accept-terms', { method: 'POST' })
close()
}}
>
Read and accept
</button>
)}
</DisclosurePanel> </Disclosure> ) }

デフォルトでは、closeを呼び出した後、DisclosureButtonがフォーカスを受け取りますが、close(ref)にrefを渡すことで変更できます。

最後に、Headless UIは、ネストされたコンポーネントなど、closeレンダープロップに簡単にアクセスできない場合に、最も近い開示の祖先を命令的に閉じることができるuseCloseフックも提供しています。

import { Disclosure, DisclosureButton, DisclosurePanel, useClose } from '@headlessui/react'

function MySearchForm() {
let close = useClose()
return ( <form onSubmit={(event) => { event.preventDefault() /* Perform search... */
close()
}}
>
<input type="search" /> <button type="submit">Submit</button> </form> ) } function Example() { return ( <Disclosure> <DisclosureButton>Filters</DisclosureButton> <DisclosurePanel> <MySearchForm /> {/* ... */} </DisclosurePanel> </Disclosure> ) }

useCloseフックは、Disclosure内にネストされたコンポーネントで使用しなければなりません。そうでなければ機能しません。

異なる要素としてレンダリングする

Disclosureとそのサブコンポーネントはそれぞれ、そのコンポーネントにとって適切なデフォルトの要素をレンダリングします。Button<button>をレンダリングし、Panel<div>をレンダリングします。対照的に、ルートDisclosureコンポーネントは要素をレンダリングせず、デフォルトで直接子要素をレンダリングします。

asプロップを使用して、コンポーネントを別の要素として、または独自のコンポーネントとしてレンダリングします。カスタムコンポーネントがrefをフォワードするようにして、Headless UIが正しく接続できるようにします。

import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
import { forwardRef } from 'react'

let MyCustomButton = forwardRef(function (props, ref) {
return <button className="..." ref={ref} {...props} />
})
function Example() { return (
<Disclosure as="div">
<DisclosureButton as={MyCustomButton}>What languages do you support?</DisclosureButton>
<DisclosurePanel as="ul">
<li>HTML</li> <li>CSS</li> <li>JavaScript</li> </DisclosurePanel> </Disclosure> ) }

キーボード操作

コマンド説明

EnterまたはSpaceキーをDisclosureButtonがフォーカスされているときに押すと。

パネルを切り替えます

コンポーネントAPI

開示

メインの開示コンポーネント。

プロップデフォルト説明
asFragment
文字列 | コンポーネント

開示がとしてレンダリングされる要素またはコンポーネント。

defaultOpenfalse
ブール値

Disclosureコンポーネントをデフォルトで開くかどうか。

データ属性レンダープロップ説明
data-openopen

ブール値

開示が開いているかどうか。

閉じる

(ref) => void

開閉表示を閉じ、DisclosureButtonにフォーカスを戻します。オプションとして、代わりにフォーカスするrefまたはHTMLElementを渡すことができます。

開閉表示を切り替えるトリガーコンポーネントです。

プロップデフォルト説明
asボタン
文字列 | コンポーネント

開示が開閉ボタン要素またはコンポーネント。

autoFocusfalse
ブール値

初回レンダリング時に開閉ボタンにフォーカスを当てるかどうか。

データ属性レンダープロップ説明
data-openopen

ブール値

初回レンダリング時にとしてレンダリングされるが開いているかどうか。

data-フォーカスフォーカス

ブール値

初回レンダリング時に開閉ボタンにフォーカスが当たっているかどうか。

data-ホバーホバー

ブール値

初回レンダリング時に開閉ボタンにマウスカーソルが乗っているかどうか。

data-アクティブアクティブ

ブール値

初回レンダリング時に開閉ボタンアクティブまたは押下状態かどうか。

data-autofocusautofocus

ブール値

autoFocusプロパティがtrueに設定されていたかどうか。

このコンポーネントは、開閉表示の内容を含んでいます。

プロップデフォルト説明
asdiv
文字列 | コンポーネント

開示が開閉パネル要素またはコンポーネント。

トランジションfalse
ブール値

data-closed data-enterdata-leaveのようなトランジション属性を要素にレンダリングするかどうか。

静的false
ブール値

内部で管理される開閉状態を無視するかどうか。

アンマウントtrue
ブール値

開閉状態に基づいて要素をアンマウントするか、非表示にするかどうか。

データ属性レンダープロップ説明
data-openopen

ブール値

初回レンダリング時にとしてレンダリングされるが開いているかどうか。

閉じる

(ref) => void

開閉表示を閉じ、DisclosureButtonにフォーカスを戻します。オプションとして、代わりにフォーカスするrefまたはHTMLElementを渡すことができます。

このボタンをクリックすると、最も近いDisclosurePanel祖先が閉じられます。あるいは、useCloseフックを使用して、開閉パネルを命令的に閉じることができます。

プロップデフォルト説明
asボタン
文字列 | コンポーネント

開示が閉じるボタン要素またはコンポーネント。

Headless UIを使用した、事前にデザインされたTailwind CSS開閉表示コンポーネントの例に興味がある場合Tailwind UIをご覧ください。これは、私たちによって構築された、美しくデザインされ、専門的に作成されたコンポーネントのコレクションです。

これは、このようなオープンソースプロジェクトへの私たちの作業を支援する素晴らしい方法であり、それらを改善し、維持することを可能にします。