Headless UI v2.0 for React

Adam Wathan
Jonathan Reinink
Headless UI v2.0

物事を改善する方法を見つける上で、実際に自分のツールを使って何か現実的なものを構築することに勝るものはありません。

ここ数ヶ月間、Catalystに取り組んできた中で、Headless UIに数多くの改善を加え、コードをさらに少なく記述できるようにし、開発者体験をさらに向上させました。

そして今回、このすべての作業の集大成であるHeadless UI v2.0 for Reactをリリースしました。

注目の新機能をご紹介します。

npmから@headlessui/reactの最新バージョンをインストールして、プロジェクトに追加してください。

npm install @headlessui/react@latest

v1.xからアップグレードする場合は、アップグレードガイドで変更点をご確認ください。


組み込みのアンカー位置指定

Floating UIをHeadless UIに直接統合したため、ドロップダウンがビューから外れたり、画面上の他の要素によって隠れたりする心配はもうありません。

MenuPopoverComboboxListboxコンポーネントの新しいanchorプロパティを使用してアンカー位置を指定し、--anchor-gap--anchor-paddingなどのCSS変数で配置を微調整します。

スクロールアップ・ダウンして、ドロップダウンの位置が変化するのを確認してください。

import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
function Example() {
return (
<Menu>
<MenuButton>Options</MenuButton>
<MenuItems
anchor="bottom start"
className="[--anchor-gap:8px] [--anchor-padding:8px]"
>
<MenuItem>
<button>Edit</button>
</MenuItem>
<MenuItem>
<button>Duplicate</button>
</MenuItem>
<hr />
<MenuItem>
<button>Archive</button>
</MenuItem>
<MenuItem>
<button>Delete</button>
</MenuItem>
</MenuItems>
</Menu>
);
}

このAPIの本当に素晴らしい点は、sm:[--anchor-gap:4px]のようなユーティリティクラスを使用してCSS変数を変更することで、異なるブレークポイントでスタイルを調整できることです。

詳細については、各コンポーネントのアンカー位置指定に関するドキュメントをご確認ください。


新しいチェックボックスコンポーネント

既存のRadioGroupコンポーネントを補完する新しいheadlessなCheckboxコンポーネントを追加し、完全にカスタムなチェックボックスコントロールを簡単に構築できるようにしました。

これにより、開発中の素晴らしい新機能にいち早くアクセスできます。

import { Checkbox, Description, Field, Label } from "@headlessui/react";
import { CheckmarkIcon } from "./icons/checkmark";
import clsx from "clsx";
function Example() {
return (
<Field>
<Checkbox
defaultChecked
className={clsx(
"size-4 rounded border bg-white dark:bg-white/5",
"data-[checked]:border-transparent data-[checked]:bg-blue-500",
"focus:outline-none data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500",
)}
>
<CheckmarkIcon className="stroke-white opacity-0 group-data-[checked]:opacity-100" />
</Checkbox>
<div>
<Label>Enable beta features</Label>
<Description>This will give you early access to any awesome new features we're developing.</Description>
</div>
</Field>
);
}

チェックボックスは、制御することも、非制御にすることもでき、非表示のinputと状態を自動的に同期させて、HTMLフォームとうまく連携させることができます。

詳細については、Checkboxドキュメントをご覧ください。


HTMLフォームコンポーネント

ネイティブのフォームコントロールをラップするだけのまったく新しいコンポーネントセットを追加しましたが、IDとaria-*属性を自動的に配線するという面倒な作業をすべて実行します。

以前は、適切に関連付けられた<label>と説明を持つ単純な<input>フィールドを構築すると、このようになっていました。

<div>
<label id="name-label" for="name-input">
Name
</label>
<input id="name-input" aria-labelledby="name-label" aria-describedby="name-description" />
<p id="name-description">Use your real name so people will recognize you.</p>
</div>

そして、Headless UI v2.0のこれらの新しいコンポーネントを使用すると、このようになります。

import { Description, Field, Input, Label } from "@headlessui/react";
function Example() {
return (
<Field>
<Label>Name</Label>
<Input name="your_name" />
<Description>Use your real name so people will recognize you.</Description>
</Field>
);
}

新しいFieldおよびFieldsetコンポーネントは、ネイティブの<fieldset>要素のようにdisabled状態をカスケードするため、コントロールのグループ全体を一度に簡単に無効にできます。

国を選択して、地域フィールドが有効になるのを確認してください。

配送先情報

現在、配送は北米のみです。

import { Button, Description, Field, Fieldset, Input, Label, Legend, Select } from "@headlessui/react";
import { regions } from "./countries";
export function Example() {
const [country, setCountry] = useState(null);
return (
<form action="/shipping">
<Fieldset>
<Legend>Shipping details</Legend>
<Field>
<Label>Street address</Label>
<Input name="address" />
</Field>
<Field>
<Label>Country</Label>
<Description>We currently only ship to North America.</Description>
<Select name="country" value={country} onChange={(event) => setCountry(event.target.value)}>
<option></option>
<option>Canada</option>
<option>Mexico</option>
<option>United States</option>
</Select>
</Field>
<Field disabled={!country}>
<Label className="data-[disabled]:opacity-40">State/province</Label>
<Select name="region" className="data-[disabled]:opacity-50">
<option></option>
{country && regions[country].map((region) => <option>{region}</option>)}
</Select>
</Field>
<Button>Submit</Button>
</Fieldset>
</form>
);
}

レンダリングされたHTMLでdata-disabled属性を使用してdisabled状態を公開しています。これにより、関連付けられた<label>要素のように、ネイティブのdisabled属性をサポートしない要素でもdisabled状態を公開できるため、各要素のdisabledスタイルを非常に簡単に微調整できます。

全体として、ここでは8つの新しいコンポーネント、FieldsetLegendFieldLabelDescriptionInputSelectTextareaを追加しました。

詳細については、Fieldsetドキュメントから始めて、残りのドキュメントに進んでください。


ホバー、フォーカス、アクティブ状態の検出を改善

内部的には素晴らしいReact Ariaライブラリのフックを使用することで、Headless UIは、ネイティブのCSS疑似クラスよりもさまざまなデバイス間でより一貫して動作する、よりスマートなdata-*状態属性をコントロールに追加するようになりました。

  • data-active:activeと同様ですが、要素からドラッグオフすると削除されます。
  • data-hover:hoverと同様ですが、タッチデバイスではスティッキーなホバー状態を避けるために無視されます。
  • data-focus:focus-visibleと同様ですが、命令的なフォーカスによる誤検出はありません。

ボタンをクリック、ホバー、フォーカス、ドラッグして、data属性が適用されるのを確認してください。

JavaScriptを使用してこれらのスタイルを適用することがなぜ重要なのかについて詳しく知るには、Devon Govett氏によるこのトピックに関する優れたブログシリーズを読むことを強くお勧めします。

Webは、実際に素晴らしいものを作るためにどれだけの努力が必要なのか、常に驚かされます。


コンボボックスのリスト仮想化

コンボボックスに10万個のアイテムを配置する必要がある場合(ボスにそうするように言われた場合)、リストの仮想化をサポートするために、TanStack VirtualをHeadless UIに統合しました。

新しいvirtualプロパティを使用してすべてのアイテムを渡し、ComboboxOptionsレンダープロパティを使用して個々のオプションのテンプレートを提供します。

コンボボックスを開き、1,000個のオプションをスクロールしてください。

import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions } from "@headlessui/react";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { useState } from "react";
const people = [
{ id: 1, name: "Rossie Abernathy" },
{ id: 2, name: "Juana Abshire" },
{ id: 3, name: "Leonel Abshire" },
{ id: 4, name: "Llewellyn Abshire" },
{ id: 5, name: "Ramon Abshire" },
// ...up to 1000 people
];
function Example() {
const [query, setQuery] = useState("");
const [selected, setSelected] = useState(people[0]);
const filteredPeople =
query === ""
? people
: people.filter((person) => {
return person.name.toLowerCase().includes(query.toLowerCase());
});
return (
<Combobox
value={selected}
virtual={{ options: filteredPeople }}
onChange={(value) => setSelected(value)}
onClose={() => setQuery("")}
>
<div>
<ComboboxInput displayValue={(person) => person?.name} onChange={(event) => setQuery(event.target.value)} />
<ComboboxButton>
<ChevronDownIcon />
</ComboboxButton>
</div>
<ComboboxOptions>
{({ option: person }) => (
<ComboboxOption key={person.id} value={person}>
{person.name}
</ComboboxOption>
)}
</ComboboxOptions>
</Combobox>
);
}

詳細については、新しい仮想スクロールのドキュメントをご覧ください。


新しいウェブサイトとドキュメントの改善

今回のメジャーリリースに伴い、ドキュメントも大幅に刷新し、ウェブサイトも一新しました。

New Headless UI v2.0 website

新しいheadlessui.comにアクセスして、ご確認ください。

最新情報を直接受信箱に届けます。
ニュースレターに登録してください。

Copyright © 2025 Tailwind Labs Inc.·商標ポリシー