開示
折りたたみ可能なアコーディオンパネルなど、コンテンツの表示と非表示を切り替えるカスタムUIを構築するためのシンプルでアクセシビリティの高い基盤です。
インストール
開始するには、npm 経由で Headless UI をインストールします。
npm install @headlessui/react
基本例
開示は、Disclosure
、DisclosureButton
、DisclosurePanel
コンポーネントを使用して構築されています。
ボタンをクリックすると、パネルが自動的に開閉され、すべてのコンポーネントは、aria-expanded
や aria-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 MotionやReact 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>
)
}
これは、次のページに移動するときに開示を閉じたいリンクを含むモバイルメニューなどのために開示を使用する場合に特に役立ちます。
Disclosure
とDisclosurePanel
は、非同期アクションの実行後など、パネルを命令的に閉じることができる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キーを | パネルを切り替えます |
プロップ | デフォルト | 説明 |
as | Fragment | 文字列 | コンポーネント 開示がとしてレンダリングされる要素またはコンポーネント。 |
defaultOpen | false | ブール値
|
データ属性 | レンダープロップ | 説明 |
data-open | open |
開示が開いているかどうか。 |
— | 閉じる |
開閉表示を閉じ、 |
プロップ | デフォルト | 説明 |
as | ボタン | 文字列 | コンポーネント 開示が開閉ボタン要素またはコンポーネント。 |
autoFocus | false | ブール値 初回レンダリング時に開閉ボタンにフォーカスを当てるかどうか。 |
データ属性 | レンダープロップ | 説明 |
data-open | open |
初回レンダリング時にとしてレンダリングされるが開いているかどうか。 |
data-フォーカス | フォーカス |
初回レンダリング時に開閉ボタンにフォーカスが当たっているかどうか。 |
data-ホバー | ホバー |
初回レンダリング時に開閉ボタンにマウスカーソルが乗っているかどうか。 |
data-アクティブ | アクティブ |
初回レンダリング時に開閉ボタンアクティブまたは押下状態かどうか。 |
data-autofocus | autofocus |
|
プロップ | デフォルト | 説明 |
as | div | 文字列 | コンポーネント 開示が開閉パネル要素またはコンポーネント。 |
トランジション | false | ブール値
|
静的 | false | ブール値 内部で管理される開閉状態を無視するかどうか。 |
アンマウント | true | ブール値 開閉状態に基づいて要素をアンマウントするか、非表示にするかどうか。 |
データ属性 | レンダープロップ | 説明 |
data-open | open |
初回レンダリング時にとしてレンダリングされるが開いているかどうか。 |
— | 閉じる |
開閉表示を閉じ、 |
プロップ | デフォルト | 説明 |
as | ボタン | 文字列 | コンポーネント 開示が閉じるボタン要素またはコンポーネント。 |
Headless UIを使用した、事前にデザインされたTailwind CSS開閉表示コンポーネントの例に興味がある場合、Tailwind UIをご覧ください。これは、私たちによって構築された、美しくデザインされ、専門的に作成されたコンポーネントのコレクションです。
これは、このようなオープンソースプロジェクトへの私たちの作業を支援する素晴らしい方法であり、それらを改善し、維持することを可能にします。