はじめに
フロントエンド開発をしていると、「このプロジェクトはReactだけど、次はVueで」みたいな状況って意外とあるんですよね。そのたびにUIライブラリを一から学び直すのは正直しんどい。
そんな悩みを解決してくれるのがArk UIです。
Ark UIは、React、Vue、Solid、Svelteの4つのフレームワークに対応したヘッドレスUIコンポーネントライブラリ。Chakra UIの開発チームが手がけていて、GitHubスターも4,900以上を獲得している注目のプロジェクトです。
個人的には「フレームワークが変わってもAPIが同じ」というのがかなり刺さりました。
とはいえ、読むより触った方が早いと思います。Ark UIのDialogコンポーネントが実際にどう動くか、その場で編集・実行できるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
特徴・メリット
1. マルチフレームワーク対応
Ark UIの最大の特徴は、4つの主要フレームワークで同じAPIが使えること。
- React
- Vue
- Solid
- Svelte
これ、意外と大きいんですよ。チームでフレームワークが統一されていない場合や、将来的に技術スタックを変更する可能性がある場合でも、学習コストを最小限に抑えられます。
2. ヘッドレス設計
スタイルが一切付いていない「ヘッドレス」なコンポーネントなので、デザインの自由度が高いです。Tailwind CSS、Panda CSS、Styled Components、Emotionなど、好きなスタイリング手法を選べます。
既存のデザインシステムに組み込みやすいのは、実務で使う上でかなりのメリットですね。
3. 45以上のプロダクション対応コンポーネント
AccordionやDialog、DatePicker、Toastなど、実務で必要になるコンポーネントが一通り揃っています。
主なカテゴリ:
- レイアウト・ナビゲーション: Accordion、Tabs、Splitter、Steps
- オーバーレイ・ダイアログ: Dialog、Popover、Tooltip、Hover Card
- フォーム・入力: Checkbox、Select、Combobox、DatePicker
- データ表示・ユーティリティ: Avatar、Progress、Carousel、Toast
4. アクセシビリティファースト
WCAG準拠でスクリーンリーダーテスト済み。アクセシビリティ対応って自分で実装すると結構大変なので、最初から組み込まれているのは時短になります。
5. Zag.jsベースの堅牢な設計
有限状態機械(finite state machines)を基盤としたZag.jsで構築されているため、コンポーネントの動作が予測可能で安定しています。Chakra UIでも実績があるので、信頼性は高いですね。
インストール方法
使用するフレームワークに応じてパッケージをインストールします。
React
npm install @ark-ui/react
# or
pnpm add @ark-ui/react
# or
yarn add @ark-ui/react
Vue
npm install @ark-ui/vue
Solid
npm install @ark-ui/solid
Svelte
npm install @ark-ui/svelte
シンプルですね。
Ark UIのサンプルを動かす
まずは実際に手を動かして、Ark UIのDialogコンポーネントがどう動くのか確認してみましょう。下のサンプルはDialog.RootのopenとonOpenChangeで開閉状態を自分のReact stateと同期させ、ダイアログの中に置いた<input>の値をダイアログの外にも表示するという、実務でもよくある構成です。パーツ構成はDialog.Trigger・Dialog.Backdrop・Dialog.Positioner・Dialog.Content・Dialog.CloseTriggerだけで、開閉のキーボード操作(Escapeで閉じるなど)やフォーカストラップはすべてArk UI側が面倒を見てくれます。
要点だけを抜き出すと、次のようになります。
import { Dialog } from '@ark-ui/react/dialog'
function ConfirmDialog() {
return (
<Dialog.Root>
<Dialog.Trigger>開く</Dialog.Trigger>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Title>タイトル</Dialog.Title>
<Dialog.Description>説明文</Dialog.Description>
<Dialog.CloseTrigger>閉じる</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
)
}
実際に動かせるのが下のサンプルです。「ダイアログを開く」ボタンを押す、名前を入力してから閉じる、といった操作を試してみてください。開閉した回数もカウントしています。
Dialog.Rootからopen/onOpenChangeを外すと、Ark UIが開閉状態を自前で管理する非制御モードになります。今回のように制御すると、開閉のたびに外部のstate(カウンターや入力値)と連動させやすくなるのが分かるはずです。Dialog.Descriptionは自動的にaria-describedbyと結びつくので、アクセシビリティ対応も追加コードなしで得られます。
基本的な使い方
Accordionコンポーネントの例(React)
import { Accordion } from '@ark-ui/react/accordion'
export const BasicAccordion = () => {
return (
<Accordion.Root>
<Accordion.Item value="item-1">
<Accordion.ItemTrigger>
セクション1
<Accordion.ItemIndicator>▼</Accordion.ItemIndicator>
</Accordion.ItemTrigger>
<Accordion.ItemContent>
ここにコンテンツが入ります。
</Accordion.ItemContent>
</Accordion.Item>
<Accordion.Item value="item-2">
<Accordion.ItemTrigger>
セクション2
<Accordion.ItemIndicator>▼</Accordion.ItemIndicator>
</Accordion.ItemTrigger>
<Accordion.ItemContent>
2つ目のコンテンツです。
</Accordion.ItemContent>
</Accordion.Item>
</Accordion.Root>
)
}
Dialogコンポーネントの例(React)
import { Dialog } from '@ark-ui/react/dialog'
export const BasicDialog = () => {
return (
<Dialog.Root>
<Dialog.Trigger>ダイアログを開く</Dialog.Trigger>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Title>確認</Dialog.Title>
<Dialog.Description>
この操作を実行しますか?
</Dialog.Description>
<Dialog.CloseTrigger>閉じる</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
)
}
Tailwind CSSでスタイリングする例
import { Slider } from '@ark-ui/react/slider'
export const StyledSlider = () => {
return (
<Slider.Root className="w-64">
<Slider.Label className="text-sm font-medium">
音量
</Slider.Label>
<Slider.Control className="relative flex items-center h-5">
<Slider.Track className="bg-gray-200 h-2 w-full rounded-full">
<Slider.Range className="bg-blue-500 h-full rounded-full" />
</Slider.Track>
<Slider.Thumb
index={0}
className="w-5 h-5 bg-white border-2 border-blue-500 rounded-full shadow"
/>
</Slider.Control>
</Slider.Root>
)
}
ヘッドレスなので、クラス名を自由に指定できるのが便利です。
実践的なユースケース
デザインシステムの構築
Ark UIはスタイルがないぶん、自社のデザインシステムに組み込みやすいです。
// 自社のButtonコンポーネントとして再エクスポート
import { Button as ArkButton } from '@ark-ui/react/button'
export const Button = ({ variant, children, ...props }) => {
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
}
return (
<ArkButton className={`px-4 py-2 rounded ${variants[variant]}`} {...props}>
{children}
</ArkButton>
)
}
フレームワーク移行時の学習コスト削減
ReactからVueに移行する場合でも、Ark UIのAPIは共通なので、コンポーネントの使い方を学び直す必要がありません。
<!-- Vue版でも同じ構造 -->
<script setup>
import { Accordion } from '@ark-ui/vue/accordion'
</script>
<template>
<Accordion.Root>
<Accordion.Item value="item-1">
<Accordion.ItemTrigger>
セクション1
</Accordion.ItemTrigger>
<Accordion.ItemContent>
コンテンツ
</Accordion.ItemContent>
</Accordion.Item>
</Accordion.Root>
</template>
複雑なフォームUI
DatePickerやComboboxなど、自前で実装すると大変なコンポーネントも用意されています。
import { DatePicker } from '@ark-ui/react/date-picker'
export const BasicDatePicker = () => {
return (
<DatePicker.Root>
<DatePicker.Label>日付を選択</DatePicker.Label>
<DatePicker.Control>
<DatePicker.Input />
<DatePicker.Trigger>📅</DatePicker.Trigger>
</DatePicker.Control>
<DatePicker.Positioner>
<DatePicker.Content>
<DatePicker.View view="day">
{/* カレンダーUI */}
</DatePicker.View>
</DatePicker.Content>
</DatePicker.Positioner>
</DatePicker.Root>
)
}
Sliderで数値をドラッグ操作するUIを作る
音量調整や画像の明るさ調整のように、数値をマウス・タッチで直感的に操作させたい場面はよくあります。Slider.RootにvalueとonValueChangeを渡すだけで、ドラッグ中の値をそのままReactのstateに反映できます。
import { Slider } from '@ark-ui/react/slider'
function VolumeSlider() {
const [value, setValue] = useState([40])
return (
<Slider.Root value={value} onValueChange={(d) => setValue(d.value)} min={0} max={100}>
<Slider.Label>音量: {value[0]}</Slider.Label>
<Slider.Control>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumb index={0} />
</Slider.Control>
</Slider.Root>
)
}
実際に動かせるのが下のサンプルです。つまみをドラッグすると音量の数値がリアルタイムで変わり、0まで下げると「ミュート」の表示に切り替わります。
min・max・stepを変えれば、そのまま値の範囲や刻み幅を調整できます。例えばstep={10}を足すと10刻みでしか止まらなくなるので、動きの粒度が変わったのが分かるはずです。onValueChangeのdetails.valueは配列で渡ってくる点に注意してください。Slider.Thumbを複数並べれば範囲選択(レンジスライダー)にも対応できます。
Comboboxで選択肢を検索して絞り込む
選択肢が多いフォームでは、プルダウンをただ並べるより、入力しながら絞り込める方が使い勝手が良くなります。ComboboxはuseListCollectionで作ったcollectionと、そのフックが返すfilter関数を組み合わせることでこれを実現します。
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
function FrameworkCombobox() {
const { collection, filter } = useListCollection({ initialItems: frameworks })
const [inputValue, setInputValue] = useState('')
return (
<Combobox.Root
collection={collection}
inputValue={inputValue}
onInputValueChange={(d) => {
setInputValue(d.inputValue)
filter(d.inputValue)
}}
>
<Combobox.Control>
<Combobox.Input placeholder="react, vue..." />
</Combobox.Control>
<Combobox.Content>
{collection.items.map((item) => (
<Combobox.Item key={item.value} item={item}>
<Combobox.ItemText>{item.label}</Combobox.ItemText>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Root>
)
}
実際に動かせるのが下のサンプルです。入力欄に「s」と打ってみてください。SolidとSvelteだけに絞り込まれるはずです。
useListCollectionが返すfilter関数にその都度の入力値を渡すだけで絞り込みロジックを自前で書かずに済むのが、ComboboxとuseListCollectionを組み合わせる利点です。frameworks配列に項目を足せば検索対象がそのまま増えますし、filterを渡さずに独自の絞り込み関数を書けば、あいまい検索など別のロジックに差し替えることもできます。
類似ライブラリとの比較
| ライブラリ | フレームワーク対応 | スタイル | コンポーネント数 |
|---|---|---|---|
| Ark UI | React/Vue/Solid/Svelte | ヘッドレス | 45+ |
| Radix UI | Reactのみ | ヘッドレス | 30+ |
| Headless UI | React/Vue | ヘッドレス | 10+ |
| shadcn/ui | Reactのみ | Tailwind | 40+ |
マルチフレームワーク対応という点では、Ark UIが一歩リードしている印象です。
まとめ
Ark UIは、複数のフレームワークで統一されたAPIを使いたい開発者にとって、かなり有力な選択肢だと思います。
こんな人におすすめ:
- 複数のフレームワークを使い分けるチーム
- 自社のデザインシステムに組み込みたい
- アクセシビリティ対応を効率化したい
- Chakra UIのような堅牢な設計が好み
30代になって思うのは、「学習コストをいかに抑えるか」って結構大事なんですよね。Ark UIはその点でかなりコスパが良いライブラリだと感じました。
公式ドキュメントも充実しているので、興味があればぜひ試してみてください。
公式サイト: https://ark-ui.com GitHub: https://github.com/chakra-ui/ark