@k8ordo/form

@k8ordo/uiと組み合わせる

@k8ordo/formは@k8ordo/uiに依存しません。その代わり、@k8ordo/uiの入力部品のほうが、formFieldsが作ったinputをそのまま受け取れるように作られています。このページでは、部品ごとの組み合わせ方と、いくつかの例外を紹介します。

このページの内容

FormControlと組み合わせる

FormControlには、ラベルとエラー、invalid、requiredだけを渡します。idやaria-*による結び付けはFormControlが作るので、自分で書く必要はありません。

talk-form.tsx
const title = form.field('title');

<FormControl
  errorText={title.error}
  invalid={title.invalid}
  label="Title"
  required={title.required}
  renderInput={(props) => <TextField {...props} {...title.input} />}
/>

inputは、FormControlから受け取ったpropsのあとに展開します。inputから取り除いておく属性はありません。TextFieldは日付や時刻のtypeも描き、Textareaは<textarea>に無いtypeを捨てます。

どの部品も値をDOMに持つので、リセットすると送信した値に戻り、isDirtyもその値を読みます。増減ボタンや選択肢の選択のように部品がコードで値を変えたときも、キー入力と同じinputイベントが届きます。

スキーマごとの部品

スキーマの型ごとに、使える部品と、{...props} {...input}のほかに渡すものは次のとおりです。

  • z.string()、z.email()、z.url()、z.iso.*():TextFieldかTextarea。ほかに渡すものはありません。
  • パスワード:PasswordInput。展開したtypeの下でも、表示を切り替えるボタンが働きます。
  • z.coerce.number():NumberFieldかSlider。文字列のminやmax、step="any"もそのまま受け取ります。
  • z.enum([…]):Select、Radio、RadioCard。optionsを渡し、ラジオボタンではFormControlにlabelAs="legend"を付けます。
  • z.boolean()、z.literal(true):CheckboxかSwitch。labelを渡し、FormControlは使いません。
  • z.array(z.enum([…])):CheckboxGroup.Root、CheckboxCard、Autocomplete。state.valuesからdefaultValueを渡します。
  • z.file():FileField.Root。FileField.TriggerとFileField.ItemListを中に置きます。

選択肢の部品

RadioとRadioCardには、inputをそのまま展開します。自分で書くラジオボタンとは違い、defaultValueを選ばれている選択肢として読み、requiredをすべてのラジオボタンに付けます。

Selectでrequiredを意味のあるものにするには、optionsの先頭に{ value: '', label: '…' }の選択肢を置きます。空の選択肢が無い<select>は、いつもどれかが選ばれているからです。

チェックボックスの集まりは、送信した値が配列で戻ります。inputにはnameしか入っていないので、チェックされていた値をdefaultValueで渡します。

tags-field.tsx
const tags = form.field('tags');
const checked = state.values?.tags;

<FormControl
  errorText={tags.error}
  invalid={tags.invalid}
  label="Tags"
  labelAs="legend"
  renderInput={(props) => (
    <CheckboxCard
      {...props}
      {...tags.input}
      defaultValue={Array.isArray(checked) ? checked : []}
      options={options}
    />
  )}
/>

minCheckedはAutocompleteにも効きます。Autocompleteは何も選んでいなくても残る隠れた<select multiple>で値を送るので、ルールがエラーを付ける要素があり、失敗したあとのフォーカスは入力欄へ移ります。

気をつける部品

警告

落とし穴

z.stringbool()の欄にCheckboxを使うと、展開したvalueが届かず、ブラウザの既定のonが送られます。既定のz.stringbool()はonをtrueと読みますが、truthyを変えた場合やURLの状態と合わせる場合は食い違います。この欄は素の<input {...field.input} />で描いてください。

NumberFieldは値を整形して増減させるためにtype="text"で描くので、ブラウザはminとmaxを確かめません。範囲の外の値はNumberField自身がsetCustomValidityで知らせますが、その文言はzodではなく@k8ordo/uiの辞書のものです。クライアントの文言がzodとそろわないのは、ここだけです。

Textareaで描く欄の正規表現はpattern属性になりますが、<textarea>はこの属性を無視します。この検証はサーバーでだけ行われ、どの部品で描くかをformFieldsは知らないので、droppedにも載りません。

FileFieldは、FileField.ItemListに表示されているファイルを送ります。一覧から外したファイルは送信からも外れ、リセットすると一覧も空になります。

フォーム全体のエラーをAlertで出す

form.formErrorはAlertで表示できます。AlertはidとtabIndexを要素まで渡すので、form.formError.propsを展開すればフォーカスを受け取れます。

talk-form.tsx
{form.formError.message !== undefined && (
  <Alert
    {...form.formError.props}
    message={form.formError.message}
    tone="error"
  />
)}

Alertはrole="alert"を持つので、表示されたときとフォーカスが移ったときの2回、スクリーンリーダーに読まれることがあります。エラーの一覧にalertロールを付けた場合と同じ割り切りです。

k8ordo

Baselineに入った機能を、制限なく使うReactのライブラリ群。

© 2026 k8o — MIT License

組版:Noto Sans JP / M PLUS 2