sashigane
コンポーネント

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 は 署名に現れないので、表に出すと「引数のデフォルトが書かれているものだけ」になり、嘘になります。

On this page