Select
セレクトボックス。一覧から1つ選ぶ
素の select ではありません
素のものは一覧の見た目を OS が描くので、揃えられません。 揃える代わりに、キーボード・フォーカス・位置決めをこちらで持っています。
そのため Select はクライアントコンポーネントです('use client')。
開閉の状態とキーボード操作を自分で持つためです。
値はフォームにそのまま載ります
見えている部分は button と ul で、値を持っていません。
値は隠した素の select が持ちます。
選んだときは本物の change を投げます。 投げないと、素のフォームには載るのに
onChange が来ません——register() は値の変化を知らないままになります。
これはテストが測っています。
<Field id="plan" label="プラン">
<Select name="plan" options={[{ value: 'a', label: 'ふつう' }]} />
</Field>react-hook-form なら {...register('plan')} をそのまま渡せます。
フォーカスは開くボタンから動きません
選択肢にフォーカスを移すと、開閉のたびにフォーカスの行き先を管理することになります。
フォーカスは開くボタンに置いたまま、いまどれを指しているかは aria-activedescendant
で伝えています。
そのぶん、いま指しているものの印は自分で描いています—— ブラウザは何も描いてくれないので、無いと矢印キーで動かしても 画面上は何も起きていないように見えます。
キーボード
| キー | すること |
|---|---|
↓ ↑ Enter Space | 開く |
↓ ↑ | 動かす(押せないものは飛ばす) |
Home End | 端へ飛ぶ |
Enter Space | 選んで閉じる |
Escape | 閉じる |
| 文字 | 打った文字で探す |
打った文字で探す操作は、かな漢字変換を通す文字では効きません。
変換中の打鍵は文字として届かないためです。素の select も同じです。
一覧は箱の中に描かれます
body へ飛ばしていません。飛ばすと面の入れ子から外れます。
代償として、overflow: hidden を持つ祖先があると一覧が切り取られます。
このページのデモでも、囲みの要素が一覧を切り取っていたので外してあります。
3つの状態
通常・空・エッジケースの3つは、どのコンポーネントにも必ずあります。
通常default.tsx
通常。素の `select` ではない。
あとから変えられます
空empty.tsx
空。選択肢が1つも無い。
エッジケースedge.tsx
エッジケース。押せない選択肢・押せない本体・誤り・とても長いラベル。
うめは選べません
選んでください
props
Prop
Type
型と説明はコンポーネントのソースから抽出しています。既定値は出していません——cva の defaultVariants は 署名に現れないので、表に出すと「引数のデフォルトが書かれているものだけ」になり、嘘になります。