Headless UI+Tailwind CSSの使い方を学ぶ(React,Vue)
ReactやVueでアプリケーションを開発している場合にドロップダウンメニューやダイアログ(モーダル)を実装したいという時に役に立つのがHeadless UIです。一度設定を行ってしまえばHeadless UIがどんなものか理解することができると思うのですが、まだ設定を行っていない段階でHeadless UIのドキュメントに記載されている概要説明”Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.”を読んだだけではあまりよくわからない人も多いかと思います。この文章の意味を完全に理解することができればHeadless UIを使いこなすことができます。
Headless UIで利用できる機能(ドロップダウンメニューやダイアログなど)は事前に用意されているコンポーネントタグを利用するだけで実装することが可能です。しかしスタイルが全く適用されていないのでTailwind CSSを利用して自分で設定を行うことでオリジナルデザインのものを作成することができます。自由にスタイルを設定を行えるだけではなくユーザのアクセスビリティも考慮に入れ実装されているので開いたドロップダウンメニューをEscキーを使うだけで閉じることができたり、ドロップダウンメニューは上下キーを押すことでマウスを使うことなくメニューの移動することができます。
実際にVite環境のReact, Vueを利用してHeadless UIに使い方を学んだ後に”Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.”の意味がスッキリ理解できることを目的に説明を行なっていきます。
Labsが開発しています。
目次
Viteによるプロジェクトの作成
Reactであればcreate-app-react, vueであればVue CLIを利用してプロジェクトを作成することができますが最近利用するユーザも増えてきているViteを利用してプロジェクトの作成を行います。
Reactの場合
Viteでプロジェクトを作成するためにnpm init vite@latestコマンドを実行します。コマンドを実行すると対話的にプロジェクト名やフレームワーク、TypeScriptを利用するかどうか確認されるので設定を行ってください。
% npm init vite@latest
Need to install the following packages:
create-vite@latest
Ok to proceed? (y) y
✔ Project name: … headless-ui-react
✔ Select a framework: › react
✔ Select a variant: › react
Scaffolding project in /...
Done. Now run:
cd headless-ui-react
npm install
npm run dev
作成されたプロジェクトフォルダに移動してnpm installコマンドを実行してJavaScriptライブラリをインストールしてnpm run devで開発サーバを起動してください。
% cd headless-ui-react
% npm install
% npm run dev
//略
vite v2.7.3 dev server running at:
> Local: http://localhost:3000/
> Network: use `--host` to expose
ブラウザでhttp://localhost:3000/にアクセスすると下記の画面が表示されます。
Vueの場合
Viteでプロジェクトを作成するためにnpm init vite@latestコマンドを実行します。コマンドを実行すると対話的にプロジェクト名やフレームワーク、TypeScriptを利用するかどうか確認されるので設定を行ってください。
% npm init vite@latest
✔ Project name: … headless-ui-vue
✔ Select a framework: › vue
✔ Select a variant: › vue
Scaffolding project in /...
Done. Now run:
cd headless-ui-vue
npm install
npm run dev
作成されたプロジェクトフォルダに移動してnpm installコマンドを実行してJavaScriptライブラリをインストールしてnpm run devで開発サーバを起動してください。
% cd headless-ui-react
% npm install
% npm run dev
//略
vite v2.7.3 dev server running at:
> Local: http://localhost:3000/
> Network: use `--host` to expose
ブラウザでhttp://localhost:3000/にアクセスすると下記の画面が表示されます。
TailwindCSSのインストール
Headless UIのスタイリングにTailwind CSSを利用するためインストールを行います。
Reactの場合
% npm install -D tailwindcss postcss autoprefixer
設定ファイルを作成するためにinitコマンドを実行します。
% npx tailwindcss init -p
Created Tailwind CSS config file: tailwind.config.js
Created PostCSS config file: postcss.config.js
tailwind.config.jsファイルを開いて下記の設定を行います。
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
index.cssを開いてtailwindのディレクティブを設定します。これまで記述されていた内容は上書きします。
@tailwind base;
@tailwind components;
@tailwind utilities;
インストール、設定が正常に完了しているか確認するためにApp.jsxファイルを更新します。
function App() {
return <h1 className="text-3xl font-bold underline">Hello world!</h1>;
}
export default App;
太文字で下線が引かれたHello Worldが画面上に表示されればTawilwind CSSの初期設定は完了です。
Vueの場合
% npm install -D tailwindcss postcss autoprefixer
設定ファイルを作成するためにinitコマンドを実行します。
% npx tailwindcss init -p
Created Tailwind CSS config file: tailwind.config.js
Created PostCSS config file: postcss.config.js
tailwind.config.jsファイルを開いて下記の設定を行います。
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
srcフォルダにindex.cssファイルを作成してtailwindのディレクティブを設定します。
@tailwind base;
@tailwind components;
@tailwind utilities;
main.jsファイルを開いた作成したindex.cssファイルをimportします。
import { createApp } from 'vue';
import App from './App.vue';
import './index.css';
createApp(App).mount('#app');
インストール、設定が正常に完了しているか確認するためにApp.vueファイルを更新します。
<template>
<h1 class="text-3xl font-bold underline">Hello world!</h1>
</template>
太文字で下線が引かれたHello Worldが画面上に表示されればTawilwind CSSの初期設定は完了です。
Headless UIのインストール
Headless UIを利用するためにはライブラリのインストールが必要となります。React, Vueではインストールするライブラリが異なります。
Reactの場合
% npm install @headlessui/react
Vueの場合
% npm install @headlessui/vue
ドロップダウンメニューの設定(React)
インストールが完了したらHeadless UIを利用しない状態でのドロップダウンメニューを構成する要素を確認しておきます。Menuという名前のボタンをクリックするとHome, Service, About us, Contactの4つのメニューが表示されるものとします。
要素をhtmlのみで記述すると下記のようになります。
function App() {
return (
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<div>
<button>Menu</button>
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/service">Service</a>
</li>
<li>
<a href="/aboutus">About us</a>
</li>
<li>
<a href="/contact">Contact</a>
</li>
</ul>
</div>
</div>
);
}
export default App;
ブラウザで確認するとスタイルが設定されていないので下記のように表示されます。
Menuコンポーネント
Headless UIではドロップダウンメニューを実装する場合はMenuコンポーネントを利用します。そのため@headlessui/reactからMenuをimportします。importしたMenuからMenu, Menu.Button, Menu.Items, Menu.Itemを利用します。先程のdivタグ、buttonタグ、ulタグ、liタグをMenuコンポーネントに置き換えます。
import { Menu } from '@headlessui/react';
function App() {
return (
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<Menu> //div
<Menu.Button>Menu</Menu.Button> //button
<Menu.Items> //ul
<Menu.Item> //li
<a href="/">Home</a>
</Menu.Item>
<Menu.Item> //li
<a href="/service">Service</a>
</Menu.Item>
<Menu.Item> //li
<a href="/aboutus">About us</a>
</Menu.Item>
<Menu.Item> //li
<a href="/contact">Contact</a>
</Menu.Item>
</Menu.Items>
</Menu>
</div>
);
}
export default App;
ブラウザで確認するとMenuという文字は表示されていますが、それ以外のHome, Serviceなどは表示されていません。
Menuの文字列をクリックしてください。Menuの文字列の下にMenu.Itemで設定したHome, Serivce, About us, Contactが表示されます。
再度Menuをクリックすると表示されていたHome, Service, About us, Contactは非表示になります。
ここまでの設定で冒頭で説明した通り”Headless UIで利用できる機能(ドロップダウンメニューやダイアログなど)は事前に用意されているコンポーネントタグを利用するだけで実装することが可能です。しかしスタイルは全く適用されていないのでTailwind CSSを利用して自分で設定を行う必要があります。”が理解できたかと思います。
Headless UIを利用しない場合はuseStateを使ってopenなどの変数は定義して、clickイベントで開閉を設定するといったことが必要になりますがHeadless UIではコンポーネント内部で設定が行われているので改めて設定を行う必要があります。
propsの設定
機能の実装ができたのではpropsを利用してタグを設定します。デフォルトではMenu.Itemsにはdiv、Menu.buttonにはbutton、Menu、Menu.Itemにはfragmentが設定されています。
ulタグ、liタグを設定したい場合はas propsを利用することができます。Menu.Itemsコンポーネントにas propsでulを設定し、Menu.Itemコンポーネントにas propsでliを設定します。
<Menu.Items as="ul">
<Menu.Item as="li">
<a href="/">Home</a>
</Menu.Item>
<Menu.Item as="li">
<a href="/service">Service</a>
</Menu.Item>
<Menu.Item as="li">
<a href="/aboutus">About us</a>
</Menu.Item>
<Menu.Item as="li">
<a href="/contact">Contact</a>
</Menu.Item>
</Menu.Items>
ul, liが適用されているので先程まで横一列に並んでいた要素が縦並びになります。as propsによってコンポーネントには指定した要素タグが設定できることがわかりました。
スタイルの設定
スタイルを設定したい場合は通常の設定と同様にclassNameを利用して設定を行うことができます。classにはTailwind CSSを利用します。
ボタンにスタイルを設定したい場合はMenu.ButtonにclassName属性を使ってclassを適用することになります。
<Menu.Button className="px-4 py-2 border rounded-lg bg-blue-600 hover:bg-blue-400 text-white font-bold">
Menu
</Menu.Button>
そのほかのコンポーネントにもスタイルを設定します。w-[200px]でドロップダウンメニューの幅を設定しています。Tailwind CSSではブラケットを利用して任意の値を設定することができます。divide-yを利用して子要素の区切り線を設定しています。
import { Menu } from '@headlessui/react';
function App() {
return (
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<Menu>
<Menu.Button className="px-4 py-2 border rounded-lg bg-blue-600 hover:bg-blue-400 text-white font-bold">
Menu
</Menu.Button>
<Menu.Items as="ul" className="w-[200px] divide-y border rounded-lg mt-2">
<Menu.Item as="li">
<a href="/" className="p-2 inline-block">Home</a>
</Menu.Item>
<Menu.Item as="li">
<a href="/service" className="p-2 inline-block">Service</a>
</Menu.Item>
<Menu.Item as="li">
<a href="/aboutus" className="p-2 inline-block">About us</a>
</Menu.Item>
<Menu.Item as="li"">
<a href="/contact" className="p-2 inline-block">Contact</a>
</Menu.Item>
</Menu.Items>
</Menu>
</div>
);
}
export default App;
さらにアイコンを追加したい場合にはheroiconsなどを利用して設定を行うことができます。heroiconsを利用したい場合にはライブラリをインストールする必要があります。
% npm install @heroicons/react
アイコンの設定を行います。
import { HomeIcon } from '@heroicons/react/outline';
//略
<Menu.Item as="li">
<a href="/" className="p-2 inline-block flex items-center">
<HomeIcon className="w-5 y-5 mr-2" />
Home
</a>
</Menu.Item>
その他のメニュー要素にもアイコンの設定を行います。
import { Menu } from '@headlessui/react';
import {
HomeIcon,
CogIcon,
UsersIcon,
PhoneIcon,
} from '@heroicons/react/outline';
function App() {
return (
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<Menu>
<Menu.Button className="px-4 py-2 border rounded-lg bg-blue-600 hover:bg-blue-400 text-white font-bold">
Menu
</Menu.Button>
<Menu.Items
as="ul"
className="w-[200px] divide-y border rounded-lg mt-2"
>
<Menu.Item as="li">
<a href="/" className="p-2 inline-block flex items-center">
<HomeIcon className="w-5 y-5 mr-2" />
Home
</a>
</Menu.Item>
<Menu.Item as="li">
<a href="/" className="p-2 inline-block flex items-center">
<CogIcon className="w-5 y-5 mr-2" />
Service
</a>
</Menu.Item>
<Menu.Item as="li">
<a href="/" className="p-2 inline-block flex items-center">
<UsersIcon className="w-5 y-5 mr-2" />
About Us
</a>
</Menu.Item>
<Menu.Item as="li">
<a href="/" className="p-2 inline-block flex items-center">
<PhoneIcon className="w-5 y-5 mr-2" />
Contact
</a>
</Menu.Item>
</Menu.Items>
</Menu>
</div>
);
}
export default App;
アイコンの表示されたメニューになります。
activeな要素へのスタイル設定
ドロップダウンのメニューの中で現在どのメニューがactiveかどうかはactive propsを利用して設定することができます。
activeの場合のみbg-blue-500で背景色を設定します。Homeは一番上のメニューでメニュー全体がborderで丸みをおびているためrounded-t-lgを設定しています。
<Menu.Item as="li" className="">
{({ active }) => (
<a
className={`${
active ? 'bg-blue-500 text-white' : 'bg-white text-black'
} p-2 w-full inline-block flex items-center rounded-t-lg`}
href="/"
>
<HomeIcon className="w-5 y-5 mr-2" />
Home
</a>
)}
</Menu.Item>
その他のメニューも上記と同様にactiveの設定を追加します。ServiceとAbout usにrounded-XXの設定はありませんが一番の下のContactにはrounded-b-lgを設定しています。
import { Menu } from '@headlessui/react';
import {
HomeIcon,
CogIcon,
UsersIcon,
PhoneIcon,
} from '@heroicons/react/outline';
function App() {
return (
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<Menu>
<Menu.Button className="px-4 py-2 border rounded-lg bg-blue-500 hover:bg-blue-400 text-white font-bold">
Menu
</Menu.Button>
<Menu.Items
as="ul"
className="w-[200px] divide-y border rounded-lg mt-2"
>
<Menu.Item as="li" className="">
{({ active }) => (
<a
className={`${
active ? 'bg-blue-500 text-white' : 'bg-white text-black'
} p-2 w-full inline-block flex items-center rounded-t-lg`}
href="/"
>
<HomeIcon className="w-5 y-5 mr-2" />
Home
</a>
)}
</Menu.Item>
<Menu.Item as="li">
{({ active }) => (
<a
className={`${
active ? 'bg-blue-500 text-white' : 'bg-white text-black'
} p-2 w-full inline-block flex items-center`}
href="/service"
>
<CogIcon className="w-5 y-5 mr-2" />
Service
</a>
)}
</Menu.Item>
<Menu.Item as="li">
{({ active }) => (
<a
className={`${
active ? 'bg-blue-500 text-white' : 'bg-white text-black'
} p-2 w-full inline-block flex items-center`}
href="/about"
>
<UsersIcon className="w-5 y-5 mr-2" />
About us
</a>
)}
</Menu.Item>
<Menu.Item as="li">
{({ active }) => (
<a
className={`${
active ? 'bg-blue-500 text-white' : 'bg-white text-black'
} p-2 w-full inline-block flex items-center rounded-b-lg`}
href="/contact"
>
<PhoneIcon className="w-5 y-5 mr-2" />
Contact
</a>
)}
</Menu.Item>
</Menu.Items>
</Menu>
</div>
);
}
export default App;
マウスでメニューを選択するとそのメニューはactiveとなるため背景色が設定されます。
Menuボタンやメニューの外側の線がフォーカスにより下記のように太線になります。
フォーカス時に表示される太線はMenu.ButtonとMenu.ItemsのclassNameにoutline-noneを追加することで表示されなくなります。
<Menu.Button className="px-4 py-2 border rounded-lg bg-blue-500 hover:bg-blue-400 text-white font-bold outline-none">
Menu
</Menu.Button>
<Menu.Items
as="ul"
className="w-[200px] divide-y border rounded-lg mt-2 outline-none"
>
アクセスビィリティの確認
マウスをメニューの上に乗せると背景色が変わりますがMenuボタンを押した後、キーボードの↑(上)、↓(下)キーを押すことでメニューを変更することができます。またメニューが開いた後にメニュー以外の要素をクリックするとメニューが閉じる以外にESCキー、Spaceキーを押してもメニューを閉じることができます。ボタンがフォーカスされている時にSpaceキーを押すとメニューの開閉を行うことができます。
Headless UIではこのような設定が行われいてることが”fully accessible UI components”という意味に関連しています。
アニメーションの設定
メニューを表示・非表示にアニメーションを設定したい場合はTransitionコンポーネントを利用することができます。Transitionコンポーネントをheadlessui/reactからimportしてMenu.Itemsを包みます。Transtionコンポーネントに下記のようにアニメーション設定を行うことでふわっとメニューが表示されるアニメーションを簡単に実装することができます。
import { Menu, Transition } from '@headlessui/react';
//略
<Transition
enter="transition duration-300 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-300 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Menu.Items
as="ul"
className="w-[200px] divide-y border rounded-lg mt-2 outline-none"
>
//略
</Menu.Items>
</Transition>
ドロップダウンメニューの設定(Vue)
コードはComposition APIのscript setupを利用して記述していきます。
インストールが完了したらHeadless UIを利用しない状態でのドロップダウンメニューを構成する要素を確認しておきます。Menuという名前のボタンをクリックするとHome, Service, About us, Contactの4つのメニューが表示されるものとします。
要素をhtmlのみで記述すると下記のようになります。
<template>
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<div>
<button>Menu</button>
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/service">Service</a>
</li>
<li>
<a href="/aboutus">About us</a>
</li>
<li>
<a href="/contact">Contact</a>
</li>
</ul>
</div>
</div>
</template>
ブラウザで確認するとスタイルが設定されていないので下記のように表示されます。
Menuコンポーネント
Headless UIではドロップダウンメニューを実装する場合はMenu, MenuButton, MenuItems, MenuItemsコンポーネントを利用します。これらのコンポーネントは@headlessui/vueからimportします。先程のdivタグをMenu、buttonタグをMenuButton、ulタグをMenuItems、liタグをMenuItemコンポーネントに置き換えます。
<script setup>
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue';
</script>
<template>
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<Menu>
<MenuButton>Menu</MenuButton>
<MenuItems>
<MenuItem>
<a href="/">Home</a>
</MenuItem>
<MenuItem>
<a href="/service">Service</a>
</MenuItem>
<MenuItem>
<a href="/aboutus">About us</a>
</MenuItem>
<MenuItem>
<a href="/contact">Contact</a>
</MenuItem>
</MenuItems>
</Menu>
</div>
</template>
ブラウザで確認するとMenuという文字は表示されていますが、それ以外のHome, Serviceなどは表示されていません。
Menuの文字列をクリックしてください。Menuの文字列の下にMenuItemコンポーネントで設定したHome, Serivce, About us, Contactが表示されます。
propsの設定
機能の実装ができたのではpropsを利用してタグを設定します。デフォルトではMenuItemsにはdiv、MenuButtonにはbutton、Menu, MenuItemには何も設定されています。
ulタグ、liタグを設定したい場合はas propsを利用することができます。MenuItemsコンポーネントにas propsでulを設定し、MenuItemコンポーネントにas propsでliを設定します。
<MenuItems as="ul">
<MenuItem as="li">
<a href="/">Home</a>
</MenuItem>
<MenuItem as="li">
<a href="/service">Service</a>
</MenuItem>
<MenuItem as="li">
<a href="/aboutus">About us</a>
</MenuItem>
<MenuItem as="li">
<a href="/contact">Contact</a>
</MenuItem>
</MenuItems>
ul, liが適用されているので先程まで横一列に並んでいた要素が縦並びになります。as propsによってコンポーネントには指定した要素タグが設定できることがわかりました。
スタイルの設定
スタイルを設定したい場合は通常の設定と同様にclass属性を利用して設定を行うことができます。classにはTailwind CSSを利用します。
ボタンにスタイルを設定したい場合はMenuButtonにclass属性を使ってclassを適用することになります。
<MenuButton
class="
px-4
py-2
border
rounded-lg
bg-blue-600
hover:bg-blue-400
text-white
font-bold
"
>Menu</MenuButton
>
そのほかのコンポーネントにもスタイルを設定します。w-[200px]でドロップダウンメニューの幅を設定しています。Tailwind CSSではブラケットを利用して任意の値を設定することができます。divide-yを利用して子要素の区切り線を設定しています。
<script setup>
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue';
</script>
<template>
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<Menu>
<MenuButton
class="
px-4
py-2
border
rounded-lg
bg-blue-600
hover:bg-blue-400
text-white
font-bold
"
>Menu</MenuButton
>
<MenuItems as="ul" class="w-[200px] divide-y border rounded-lg mt-2">
<MenuItem as="li">
<a href="/" className="p-2 inline-block">Home</a>
</MenuItem>
<MenuItem as="li">
<a href="/service" className="p-2 inline-block">Service</a>
</MenuItem>
<MenuItem as="li">
<a href="/aboutus" className="p-2 inline-block">About us</a>
</MenuItem>
<MenuItem as="li">
<a href="/contact" className="p-2 inline-block">Contact</a>
</MenuItem>
</MenuItems>
</Menu>
</div>
</template>
さらにアイコンを追加したい場合にはheroiconsなどを利用して設定を行うことができます。heroiconsを利用したい場合にはライブラリをインストールする必要があります。
% npm install @heroicons/vue
最初のメニューにホームアイコンを設定します。
<MenuItem as="li">
<a href="/" class="p-2 inline-block flex items-center">
<HomeIcon class="w-5 y-5 mr-2" />
Home
</a>
</MenuItem>
その他のメニュー要素にもアイコンの設定を行います。
<script setup>
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue';
import {
HomeIcon,
CogIcon,
UsersIcon,
PhoneIcon,
} from '@heroicons/vue/outline';
</script>
<template>
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<Menu>
<MenuButton
class="
px-4
py-2
border
rounded-lg
bg-blue-600
hover:bg-blue-400
text-white
font-bold
"
>Menu</MenuButton
>
<MenuItems as="ul" class="w-[200px] divide-y border rounded-lg mt-2">
<MenuItem as="li">
<a href="/" class="p-2 inline-block flex items-center">
<HomeIcon class="w-5 y-5 mr-2" />
Home
</a>
</MenuItem>
<MenuItem as="li">
<a href="/" class="p-2 inline-block flex items-center">
<CogIcon class="w-5 y-5 mr-2" />
Service
</a>
</MenuItem>
<MenuItem as="li">
<a href="/" class="p-2 inline-block flex items-center">
<UsersIcon class="w-5 y-5 mr-2" />
About Us
</a>
</MenuItem>
<MenuItem as="li">
<a href="/" class="p-2 inline-block flex items-center">
<PhoneIcon class="w-5 y-5 mr-2" />
Contact
</a>
</MenuItem>
</MenuItems>
</Menu>
</div>
</template>
アイコンの表示されたメニューが表示されます。
activeな要素へのスタイル設定
ドロップダウンのメニューの中で現在どのメニューがactiveかどうかはv-slotを利用して設定することができます。
activeの場合のみbg-blue-500で背景色を設定します。Homeは一番上のメニューでメニュー全体がborderで丸みをおびているためrounded-t-lgを設定しています。
<MenuItem as="li" v-slot="{ active }">
<a
href="/"
:class="active ? 'bg-violet-500 text-white' : 'text-gray-900'"
class="p-2 inline-block flex items-center"
>
<HomeIcon class="w-5 y-5 mr-2" />
Home
</a>
</MenuItem>
その他のメニューも上記と同様にv-slotとactiveの設定を追加します。ServiceとAbout usにrounded-XXの設定はありませんが一番の下のContactにはrounded-b-lgを設定しています。
<script setup>
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue';
import {
HomeIcon,
CogIcon,
UsersIcon,
PhoneIcon,
} from '@heroicons/vue/outline';
</script>
<template>
<div className="m-8">
<h1 className="text-xl font-bold">ドロップダウンメニュー</h1>
<Menu>
<MenuButton
class="
px-4
py-2
border
rounded-lg
bg-blue-600
hover:bg-blue-400
text-white
font-bold
"
>Menu</MenuButton
>
<MenuItems as="ul" class="w-[200px] divide-y border rounded-lg mt-2">
<MenuItem as="li" v-slot="{ active }">
<a
href="/"
:class="active ? 'bg-violet-500 text-white' : 'text-gray-900'"
class="p-2 inline-block flex items-center"
>
<HomeIcon class="w-5 y-5 mr-2" />
Home
</a>
</MenuItem>
<MenuItem as="li" v-slot="{ active }">
<a
href="/"
:class="active ? 'bg-violet-500 text-white' : 'text-gray-900'"
class="p-2 inline-block flex items-center"
>
<CogIcon class="w-5 y-5 mr-2" />
Service
</a>
</MenuItem>
<MenuItem as="li" v-slot="{ active }">
<a
href="/"
:class="active ? 'bg-violet-500 text-white' : 'text-gray-900'"
class="p-2 inline-block flex items-center"
>
<UsersIcon class="w-5 y-5 mr-2" />
About Us
</a>
</MenuItem>
<MenuItem as="li" v-slot="{ active }">
<a
href="/"
:class="active ? 'bg-violet-500 text-white' : 'text-gray-900'"
class="p-2 inline-block flex items-center"
>
<PhoneIcon class="w-5 y-5 mr-2" />
Contact
</a>
</MenuItem>
</MenuItems>
</Menu>
</div>
</template>
マウスでメニューを選択するとそのメニューはactiveとなるため背景色が設定されます。
Menuボタンやメニューの外側の線がフォーカスにより下記のように太線になります。
フォーカス時に表示される太線はMenuButtonとMenuItemsのclassにoutline-noneを追加することで表示されなくなります。
<MenuButton
class="
px-4
py-2
border
rounded-lg
bg-blue-600
hover:bg-blue-400
text-white
font-bold
outline-none
"
>Menu</MenuButton
>
<MenuItems
as="ul"
class="w-[200px] divide-y border rounded-lg mt-2 outline-none"
>
アクセスビィリティの確認
マウスをメニューの上に乗せると背景色が変わりますがMenuボタンを押した後、キーボードの↑(上)、↓(下)キーを押すことでメニューを変更することができます。またメニューが開いた後にメニュー以外の要素をクリックするとメニューが閉じる以外にESCキー、Spaceキーを押してもメニューを閉じることができます。ボタンがフォーカスされている時にSpaceキーを押すとメニューの開閉を行うことができます。
Headless UIではこのような設定が行われいてることが”fully accessible UI components”という意味に関連しています。
アニメーションの設定
メニューを表示・非表示にアニメーションを設定したい場合はVueが持っているtransitionコンポーネントを利用します。下記のようにMenuItemsコンポーネントをtransitionコンポーネントで包むことでアニメーション設定を行うことでふわっとメニューが表示されるアニメーションを簡単に実装することができます。
<transition
enter-active-class="transition duration-300 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-300 ease-out"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<MenuItems
as="ul"
class="w-[200px] divide-y border rounded-lg mt-2 outline-none"
>
//略
</MenuItems>
</transition>
まとめ
ReactかVueの設定のどちらかを読み進めて”Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.”の意味を理解することができましたか?
一度Headless UIを設定を行うことで上記の文章を理解することができれば実装を考える必要なくスタイルを自由にカスタイズすることができるのでどこかでプロジェクトで利用してみようかなという気になってもらえたのではないでしょうか。他のコンポーネントについても機能は実装できてもスタイルが設定されていないので最初は少し戸惑うかもしれませんが手を動かしながらスタイルを設定していくことで使いこなすことができると思うので他の機能もぜひチャレンジしてみてください。