@k8ordo/ui

フォーム

@k8ordo/formのform.field(name).inputを、@k8ordo/uiの入力コンポーネントにそのまま展開できるようになります。スキーマごとにどのコンポーネントを使い、何を足して渡すかが分かります。

このページの内容

inputの展開

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} />}
/>

form.field(name).inputには、@k8ordo/formがスキーマから導いたtypeやrequiredなどの属性が入っています。useFormの使い方は、@k8ordo/formのはじめるを見てください。

FormControlにはlabelとerrorText、invalid、requiredだけを渡します。idとaria-*の結び付けはFormControlが作ります。inputはFormControlから受け取ったpropsのあとに展開します。取り除いておく属性はありません。TextFieldは日付や時刻のtypeでも、そのtypeの入力欄として描画します。Textareaは<textarea>に無いtypeを捨てます。

どのコンポーネントも非制御で使えます。値は入力要素に入っているので、リセットするとdefaultValueに戻ります。isDirtyもdefaultValueと比べます。増減ボタンや選択肢の操作でコンポーネントが値を変えたときも、キー入力と同じ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。defaultValueを渡します。
  • z.file():FileField.Root。FileField.TriggerとFileField.ItemListを中に置きます。

選択肢のコンポーネント

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}
    />
  )}
/>

チェックボックスの集まりの値は、送信後にstate.valuesへ配列で入ります。inputにはnameしか入っていないので、この配列をdefaultValueで渡します。minCheckedはAutocompleteにも効きます。Autocompleteは、何も選んでいなくても残る隠れた<select multiple>で値を送り、エラーはこの要素に付きます。失敗したあとのフォーカスは、この要素から入力欄へ移ります。

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

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

注意点

警告

落とし穴

z.stringbool()のフィールドにCheckboxを使うと、展開したvalueが<input>に渡らず、ブラウザの既定のonが送られます。z.stringbool()は初期設定のままならonをtrueと読みます。truthyを変えると、送られたonはスキーマに合わない値になります。@k8ordo/stateがURLに書く値とも一致しません。このフィールドは<input {...field.input} />で直接描画してください。

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

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

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

フォーム全体のエラー

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

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

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

k8ordo

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

© 2026 k8o — MIT License

組版:Noto Sans JP / M PLUS 2