Headless UI を試してみる
とりあえず Vue で試してみる
感想
Atomic Design とかやっていると、基礎的なコンポーネントの実装が意外にも実装難易度が高いなあと感じることがあった。例えばうっかり esc キーで閉じないモーダルを作ってしまうとか
その中でスタイルを抜きにした Headless UI はこれを使っていればその心配がないのでとても良いと思った
ただ、Headless UI は Tailwind UI ありきな OSS っぽいので、今後どんな風に発展していくか不安になるところはある
一番左上にあった Menu を試す
<template>
<Menu>
<MenuButton>More</MenuButton>
<MenuItems>
<MenuItem v-slot="{ active }">
<a :class='{ "bg-blue-500": active }' href="/account-settings">
Account settings
</a>
</MenuItem>
<MenuItem v-slot="{ active }">
<a :class='{ "bg-blue-500": active }' href="/account-settings">
Documentation
</a>
</MenuItem>
<MenuItem disabled>
<span class="opacity-75">Invite a friend (coming soon!)</span>
</MenuItem>
</MenuItems>
</Menu>
</template>
装飾のないメニューになる
class は書かれているけど、tailwind css を導入していないので何も装飾がない
<template>
<Menu>
<MenuButton>More</MenuButton>
<MenuItems>
<MenuItem v-slot="{ active }">
<a :class="[active ? 'active' : 'in-active']" href="/account-settings">
Account settings: {{ active }}
</a>
</MenuItem>
<MenuItem v-slot="{ active }">
<a :class="{ 'bg-blue-500': active }" href="/account-settings">
Documentation
</a>
</MenuItem>
<MenuItem disabled>
<span class="opacity-75">Invite a friend (coming soon!)</span>
</MenuItem>
</MenuItems>
</Menu>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { Menu, MenuButton, MenuItems, MenuItem } from "@headlessui/vue";
export default defineComponent({
components: {
Menu,
MenuButton,
MenuItems,
MenuItem,
},
});
</script>
<style>
.active {
color: red;
}
.in-active {
color: yellow;
}
</style>
試しに CSS を書いてみる
マウスオーバーすると赤色になる
DOM はこんな感じ
aria-*
とか role
属性がついていい感じ
Accessibility notes
Focus management
Clicking the MenuButton toggles the menu and focuses the MenuItems component. Focus is trapped within the open menu until Escape is pressed or the user clicks outside the menu. Closing the menu returns focus to the MenuButton.
フォーカスがよしなに移動して良さそう
List を試してみる
サンプルコード
<template>
<Listbox v-model="selectedPerson">
<ListboxButton>{{ selectedPerson.name }}</ListboxButton>
<ListboxOptions>
<ListboxOption
v-for="person in people"
:key="person"
:value="person"
:disabled="person.unavailable"
>
{{ person.name }}
</ListboxOption>
</ListboxOptions>
</Listbox>
</template>
<script>
import { ref } from "vue";
import {
Listbox,
ListboxButton,
ListboxOptions,
ListboxOption,
} from "@headlessui/vue";
export default {
components: { Listbox, ListboxButton, ListboxOptions, ListboxOption },
setup() {
const people = [
{ id: 1, name: "Durward Reynolds", unavailable: false },
{ id: 2, name: "Kenton Towne", unavailable: false },
{ id: 3, name: "Therese Wunsch", unavailable: false },
{ id: 4, name: "Benedict Kessler", unavailable: true },
{ id: 5, name: "Katelyn Rohan", unavailable: false },
];
const selectedPerson = ref(people[0]);
return {
people,
selectedPerson,
};
},
};
</script>
Menu と役割被ってない??
ul/li で実装していてえらい
https://headlessui.dev/vue/dialog を試す
わかりにくいけど装飾がなにもない dialog が表示されている
DOM の様子
DialogOverlay
とか実装は面倒だけど毎回必要になるやつがよしなに用意されている