Formについて学ぶ（少しだけアクセシビリティ）

# Formについて学ぶ（少しだけアクセシビリティ）
よく考えたらFormのこと何もしらなかったので

# 注意
想像で書いてる部分もあるので、間違ってたら直します
見つけたら教えていただければと思います

# 検証用環境
以下のリポジトリ、環境を使います
https://github.com/stopod/rr-chakra-conform-sample
https://qiita.com/stopod/items/42ac91a67037e849e97f
※HTMLの基礎みたいなことなのでここはなんでもいいです

# やっていくこと
- これを読む
https://developer.mozilla.org/ja/docs/Learn/Forms
適宜以下も読みます
https://developer.mozilla.org/ja/docs/Learn/Accessibility
- 記載されてるコードを真似る
- ChakraUIで代替してみる

# やらないこと
- css関連（例外はある）
- わからなさすぎることの深堀り

# 本題（Formについて）
ここからはまず以下を読みます
しばらくは下記の章ごとに進めます
https://developer.mozilla.org/ja/docs/Learn/Forms

## 初めてのフォーム
ひとまずサンプルのフォームを書きました

```tsx
import { Form } from "react-router";
import { Route } from "./+types/sample-form";
import { Input, Stack, Textarea, Button } from "@chakra-ui/react";

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  console.log(formData);
  return formData;
}

export default function FormTry() {
  return (
    <Form action="/sample-form" method="post">
      <Stack gap={4} m={8}>
        <label htmlFor="name">Name:</label>
        <Input type="text" id="name" name="user_name" />

        <label htmlFor="mail">Email:</label>
        <Input type="email" id="mail" name="user_email" />

        <label htmlFor="msg">Message:</label>
        <Textarea id="msg" name="user_message" />
      </Stack>

      <Button type="submit">メッセージを送信</Button>
    </Form>
  );
}

```

適宜コンポーネントはreact-routerのだったりChakraのだったりに置き換えていますが、似たような挙動（だと思っている）だと思っています

以下がまとめです
- `<Form>`の`action`に設定されたURLへデータが送信される
(このURLにあるスクリプトがデータを受け取る)
- `name`を設定しないと送信されない
- `<labl>`の`for`(reactなら`htmlFor`)と`<input>`の`id`を同一にしておくと、ラベルをクリックしても`input`がアクティブになる
`<label>`の子要素にしても同様のことが出来るが、`id`は紐づけたほうが良い
https://developer.mozilla.org/ja/docs/Web/HTML/Attributes/for

## フォームの構築方法
（ここから`<Form>`内のコードを書くときは`<Form>`タグ以下を書いていきます）

```tsx
<Stack gap={8}>
  <Box>
    <Heading>Payment form</Heading>
    <Text>
      Required fields are followed by
      <Mark aria-label="required" variant={"plain"} fontWeight={"bold"}>
        *
      </Mark>
      .
    </Text>
  </Box>
  <section>
    <Heading>Contact information</Heading>
    <fieldset>
      <legend>Title</legend>
      <RadioGroup name="title">
        <HStack gap={4}>
          <Radio id="title_1" value="A">
            Ace
          </Radio>
          <Radio id="title_2" value="K">
            King
          </Radio>
          <Radio id="title_3" value="Q">
            Queen
          </Radio>
        </HStack>
      </RadioGroup>
    </fieldset>
    <Box>
      <label htmlFor="name">
        <Text>
          Name:
          <Mark aria-label="required" variant={"plain"} fontWeight={"bold"}>
            *
          </Mark>
        </Text>
      </label>
      <Input type="text" id="name" name="username" required />
    </Box>
    <Box>
      <label htmlFor="mail">
        <Text>
          Email:
          <Mark aria-label="required" variant={"plain"} fontWeight={"bold"}>
            *
          </Mark>
        </Text>
      </label>
      <Input type="email" id="mail" name="usermail" required />
    </Box>
    <Box>
      <label htmlFor="pwd">
        <Text>
          Password:
          <Mark aria-label="required" variant={"plain"} fontWeight={"bold"}>
            *
          </Mark>
        </Text>
      </label>
      <Input type="password" id="pwd" name="password" required />
    </Box>
  </section>
  <section>
    <Heading>Payment information</Heading>
    <Box>
      <label htmlFor="card">
        <Text>Card type: </Text>
      </label>
      <NativeSelectRoot>
        <NativeSelectField id="card" name="usercard">
          <option value="visa">Visa</option>
          <option value="mc">Mastercard</option>
          <option value="amex">American Express</option>
        </NativeSelectField>
      </NativeSelectRoot>
    </Box>
    <Box>
      <label htmlFor="number">
        <Text>
          Card number:
          <Mark aria-label="required" variant={"plain"} fontWeight={"bold"}>
            *
          </Mark>
        </Text>
      </label>
      <Input type="tel" id="number" name="cardnumber" required />
    </Box>
    <Box>
      <label htmlFor="expiration">
        <Text>
          Expiration date:
          <Mark aria-label="required" variant={"plain"} fontWeight={"bold"}>
            *
          </Mark>
        </Text>
      </label>
      <Input
        type="text"
        id="expiration"
        name="expiration"
        required
        placeholder="MM/YY"
        pattern="^(0[1-9]|1[0-2])\/([0-9]{2})$"
      />
    </Box>
  </section>
  <section>
    <Button type="submit">Validate the payment</Button>
  </section>
</Stack>
```

前の章と同様に適宜Chakraのコンポーネントを使用しています
`<Field`, `<Fieldset>`がChakraにも用意されてますがこの段階ではいったん無視します

- `<fieldset>`で`<Form>`の要素をグループ化することができる、
この際のラベル的な役割を果たしているのが`<legend>`
https://developer.mozilla.org/ja/docs/Learn/Forms/How_to_structure_a_web_form#fieldset_%E3%81%8A%E3%82%88%E3%81%B3_legend_%E8%A6%81%E7%B4%A0
でもそもそも`<fieldset>`が複数出てくるようなページにするべきではないと読み取った


- `aria-label`をつけることで読み上げ時の音を決めてる（*となっているが、これを「必須」と読み上げさせる的な感じの理解）


- `<section>`は区切りの意味、Chakraの`<Vstack>`で代用しようとしたけどブラウザの開発者ツールで見たら別に`<section>`とかになってるわけじゃなかった（それはそう、あくまでChakraはUIコンポーネントなので）
https://developer.mozilla.org/ja/docs/Web/HTML/Element/section


- `<section>`には見出しを付けるのが好ましいらしい
Chakraには`<Heading>`が用意されてるので、これが見出し(`<h2>`とか)の代わりになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/b261d6e0-fcea-fdc2-37e8-a4719641caf5.png)


- `<label>`の中にコンポーネントを入れることもできる
https://developer.mozilla.org/ja/docs/Learn/Forms/How_to_structure_a_web_form#label_%E8%A6%81%E7%B4%A0

## 基本的なネイティブフォームコントロール
- `readonly`は送信される、`disabled`は送信されない
https://developer.mozilla.org/ja/docs/Learn/Forms/Basic_native_form_controls#%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E5%85%A5%E5%8A%9B%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89

- `<fieldset>`を多用しないほうが良いのかもしれないと前述したが、
typeが`radio`, `checkbox`はまとめるために使う必要がある
（こいつらはそれぞれが`<input>`要素なので、1つの塊にする必要があるイメージを持った）
https://developer.mozilla.org/ja/docs/Learn/Forms/Basic_native_form_controls#%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E5%8F%AF%E8%83%BD%E9%A0%85%E7%9B%AE_%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%81%A8%E3%83%A9%E3%82%B8%E3%82%AA%E3%83%9C%E3%82%BF%E3%83%B3

↓こんな感じでラジオボタンは複数のinputをくっつけて1つの入力要素としてる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/eab76f47-6bd8-ba04-ad2b-6a7977ba3719.png)


## HTML5 の入力型

```tsx
<Stack gap={4} m={8}>
  <label htmlFor="email">Email:</label>
  <Input type="email" id="email" name="email" />
  
  <label htmlFor="search">Search:</label>
  <Input type="search" id="search" name="search" />
  
  <label htmlFor="tel">Tel:</label>
  <Input type="tel" id="tel" name="tel" />
  
  <label htmlFor="url">Url:</label>
  <Input type="url" id="url" name="url" />
  
  <label htmlFor="datetime-local">Datetime-local:</label>
  <Input type="datetime-local" name="datetime" id="datetime" />
  
  <label htmlFor="month">Month:</label>
  <Input type="month" name="month" id="month" />
  
  <label htmlFor="time">Time:</label>
  <Input type="time" name="time" id="time" />
  
  <label htmlFor="week">Week:</label>
  <Input type="week" name="week" id="week" />
  
  <label htmlFor="color">Color:</label>
  <Input type="color" name="color" id="color" />
  
  <Button type="submit">Enter</Button>
</Stack>
```

`<input>`にはたくさんの`type`があるって話ですね

こんな見た目になりました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/3bbd035d-3319-2578-94d6-44a7a86e21c6.png)

送信されるデータは以下です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/b22eb05b-3264-3be2-1e54-0c63f7547971.png)
weekは1週間目を基準としてそこからひたすらに加算していく感じみたいですね、
いつ使うんでしょう？でも標準に存在しているということは一定の需要はあるということなんでしょうね

`tel`はテンキーが出てくるだけで特に入力制御はないみたいですね、スマホなどでは入力の仮想キーボードが数値だけとかになるっぽいです キーボード入力してるとちょっとよくわからないですね


日付関連は意外と細かく用意されています、週まであるのは驚きです
見た目さえ気にしなければ標準のものを使うのが良い気持ちです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/666d0557-ca99-9f92-fdd5-948ed2e153b0.png)


## その他のフォームコントロール

```tsx
<Stack gap={4} m={8}>
  {/* テキストエリア */}
  <label htmlFor="textarea">Textarea:</label>
  <Textarea id="textarea" name="textarea" />
  {/* いつものセレクトボックス */}
  <label htmlFor="simple">SimpleSelect:</label>
  <NativeSelectRoot>
    <NativeSelectField id="simple" name="simple" defaultValue={"さくらんぼ"}>
      <option>バナナ</option>
      <option>さくらんぼ</option>
      <option>レモン</option>
    </NativeSelectField>
  </NativeSelectRoot>
  
  {/* グループ化されたセレクトボックス */}
  <label htmlFor="groups">GroupSelect:</label>
  <NativeSelectRoot>
    <NativeSelectField id="groups" name="groups" defaultValue={"さくらんぼ"}>
      <optgroup label="果物">
        <option>バナナ</option>
        <option>さくらんぼ</option>
        <option>レモン</option>
      </optgroup>
      <optgroup label="野菜">
        <option>人参</option>
        <option>茄子</option>
        <option>馬鈴薯</option>
      </optgroup>
    </NativeSelectField>
  </NativeSelectRoot>
  
  {/* 複数選択可能なセレクトボックス */}
  <label htmlFor="multi">MultiSelect:</label>
  <NativeSelectRoot>
    <NativeSelectField id="multi" name="multi" multiple>
      <optgroup label="果物">
        <option>バナナ</option>
        <option>さくらんぼ</option>
        <option>レモン</option>
      </optgroup>
      <optgroup label="野菜">
        <option>人参</option>
        <option>茄子</option>
        <option>馬鈴薯</option>
      </optgroup>
    </NativeSelectField>
  </NativeSelectRoot>
  
  {/* 自動補完されるセレクトボックス、入力が可能 */}
  <label htmlFor="myFruit">好きな果物は何ですか？</label>
  <Input type="text" name="myFruit" id="myFruit" list="mySuggestion" />
  <datalist id="mySuggestion">
    <option>リンゴ</option>
    <option>バナナ</option>
    <option>ブラックベリー</option>
    <option>ブルーベリー</option>
    <option>レモン</option>
    <option>ライチ</option>
    <option>桃</option>
    <option>梨</option>
  </datalist>
  
  {/* 自動補完されるセレクトボックス、古いブラウザ対応版 */}
  <label htmlFor="myFruit2">好きな果物は何ですか？（代替手段付き）</label>
  <Input type="text" id="myFruit2" name="fruit" list="fruitList" />
  <datalist id="fruitList">
    <label htmlFor="suggestion">または果物を選択</label>
    <NativeSelectRoot>
      <NativeSelectField id="suggestion" name="altFruit">
        <option>リンゴ</option>
        <option>バナナ</option>
        <option>ブラックベリー</option>
        <option>ブルーベリー</option>
        <option>レモン</option>
        <option>ライチ</option>
        <option>桃</option>
        <option>梨</option>
      </NativeSelectField>
    </NativeSelectRoot>
  </datalist>
  
  <label htmlFor="meter">meter</label>
  <meter min="0" max="100" value="75" low={33} high={66} optimum={0} id="meter">
    75
  </meter>
  
  <label htmlFor="progress">progress</label>
  <progress max="100" value="75" id="progress">
    75/100
  </progress>
  <Button type="submit">Submit</Button>
</Stack>
```

### テキストエリア
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/6f5fea9d-e7a9-1707-8f87-ddcb903c2306.png)
素だとchildrenの位置に`value`を持つけどChakraだと違うみたいです
（こっちの方がわざわざ閉じタグ付けなくていいので便利）

あと`cols`、`rows`で縦幅横幅を行、文字数で管理できるっぽいです
ただ`<Stack>`配下にて`cols`を設定しても`<Stack>`のfullサイズになってるような気がします
（`<Stack>`から外れた場所で設定したら`cols`の設定通りに幅が変わった）

`wrap`の設定で折り返しの方法を指定できるらしい(改行コードを入れるかどうかとか)

### セレクトボックス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/5536e852-b9d0-596d-1a38-b99cad21567d.png)
標準にグループ化できるタグがあったんですね...
この例では`value`を指定していないのでchildrenの値が送信されますが、`value`を設定してたら`value`の値が送信されます （valueを明示的に設定しなかったらchildrenがvalueの値される、が正しい？）


### 複数選択可能なセレクトボックス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/eda95ff4-431b-5b5a-9c2d-3d32157608f3.png)

`<select>`タグに`multiple`を付けると出来ます、見える量は`size`にて変更可能
`multiple`を付けると見え方が若干変わって、クリックしたら一覧がばって出てくるわけじゃないみたいです

Chakraの[NativeSelect](https://www.chakra-ui.com/docs/components/native-select)を使うと`size`が大きさ変更で枠を埋められてるので実質的に使用不可（使えるけど見た目的にアレ）
Chakraでこれを使うならNativeSelectじゃない方の[Select](https://www.chakra-ui.com/docs/components/select)を使う必要があります

### 自動補完のボックス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/faf22bc4-1108-153c-e7e8-d7aa5bda86fd.png)
一番意味がわからないです、Edgeだと最初はそれっぽく表示されましたが、リストにない文字列を一度入力して送信すると、それ以降リストが表示されなくなりました どういうこと
Chromeだと表示されました、でもちょっと見た目がアレ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/1fc6d184-b5c4-ce95-ca2e-ad2491f88ecd.png)

見た目に関してはCSS関係を何もしてないだとも思いますが、おとなしくreact-selectとか違うライブラリを使った方が良い気持ちになりました、ただ標準で`<datalist>`なるタグがあることに気づけたのは良かったです

<br><br><br>
メーターとプログレスバーは割愛します、プログレスバーはChakraにもありました
この辺は適宜思い出して使えば良いと思います

## ウェブフォームへのスタイル設定
今回は学びたいことから逸れるので省きます

## フォームへの高度なスタイル設定
今回は学びたいことから逸れるので省きます

## UI疑似クラス
今回は学びたいことから逸れるので省きます
省きますが読んだ感想としてはreactとかChakraとかconformとかって外部ライブラリって便利だなと
これを読んだあとだと一層ありがたみを感じますね

## クライアント側のフォーム検証
フォーム検証についてをまとめます
ここではクライアント側の検証について述べられてます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/dc132820-fa25-f627-31f6-a046ffc9b089.png)
※組込みフォーム検証の下に置いたスクリーンショットは「[組み込みフォーム検証の利用](https://developer.mozilla.org/ja/docs/Learn/Forms/Form_validation#%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E6%A4%9C%E8%A8%BC%E3%81%AE%E5%88%A9%E7%94%A8)」から引っ張りました

クライアント側の検証はユーザーのためにやっておいてね、サーバー側の検証は自分たちのためにやりなねってことですね（超要約）

### 組み込みフォーム検証
ひとまず組み込みフォーム検証を種類ごとに見ていきます

#### required
必須項目にするやつです

```tsx
<Stack gap={4} m={8}>
  <label htmlFor="choose">banana と cherry のどちらが好き?</label>
  <Input id="choose" name="i-like" required />
  <Button type="submit">Submit</Button>
</Stack>
```

```css
input:invalid {
  border: 2px dashed red;
}

input:valid {
  border: 2px solid black;
}
```
入力した状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/d932cb70-c6d7-e097-a8b2-7dff360206e1.png)

入力せずsubmitした状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/a2d11207-3489-4d74-8f07-a3f77e8c61e5.png)


こんな感じで試しました
`required`を付与すると、見た目に変化はありませんが空でsubmitをしても送信されません
つまり元から`<input>`に備わってる機能(= 組み込み)だということなのでしょう

また、cssをあてることで空の時(= バリデーションに引っかかってる状態)では赤枠となり、それ以外では黒枠となりました
Chakra等のUIライブラリを使ってると当たり前のようにこの辺りをぽいぽいやってますが、自分で書くと地味に面倒ですね

ちなみにvalidが有効という意味で、invalidが無効という意味です
なのでcssは有効の時は黒枠で無効の時は赤枠にしてねってことですね

追加で下記のようにします
```css
input:invalid {
  border: 2px dashed red;
}

/* ↓追加 */
input:invalid:required {
  background-image: linear-gradient(to right, pink, lightgreen);
}

input:valid {
  border: 2px solid black;
}
```

入力していない状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/f073e9a5-fdce-2af3-de81-533983ea3a9b.png)


このように`<input>`が無効の時の`required`が付いてるやつという指定も出来るみたいです

#### pattern
入力された文字が求めてるものかをチェックするやつです

`required`に加えて`pattern`を追加します
```tsx
<Stack gap={4} m={8}>
  <label htmlFor="choose">banana と cherry のどちらが好き?</label>
  <Input id="choose" name="i-like" required pattern="[Bb]anana|[Cc]herry" />
  <Button type="submit">Submit</Button>
</Stack>
```

何も入力していない状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/24aa8c8f-605b-cb0d-49f3-39589352ef09.png)

正規表現で引っかかった状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/a4a99f91-8592-0fd4-228f-6b5f302e420d.png)

正規表現で引っかかってsubmitした状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/cf2801bb-9b3d-7ab1-a92a-38ab4ecc4e35.png)


正規表現で問題なかった状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/f0bf4692-b3dc-f93c-11a5-75a6c2881cfc.png)

入力された内容に応じてバリデーションが出来るようになりました

#### minLength, maxLengtn
入力文字数制限です

2文字以上、10文字以下制限にしました
```tsx
<Stack gap={4} m={8}>
  <label htmlFor="len">len:</label>
  <Input id="len" name="len" minLength={2} maxLength={10} />
  <Button type="submit">Submit</Button>
</Stack>
```

未入力状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/8ba5b273-04db-b220-5927-6d80c94969d4.png)

1文字入力状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/231959a5-c334-e08d-fded-e5456b7e1894.png)

2文字入力状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/72c9fd77-e7e0-d320-f5f9-ceafc1c6b390.png)

10文字以上入力しようとしている状態
ここから確定(Enter)すると10文字に入力が切られます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/f342933c-9883-8b83-ed37-78955ccba84d.png)

でもMDNを読んでると、これより良い実装方法があるらしく -> [ここ](https://developer.mozilla.org/ja/docs/Learn/Forms/Form_validation#%E5%85%A5%E5%8A%9B%E6%AC%84%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AE%E5%88%B6%E7%B4%84)
> ブラウザーはよくテキストフィールドに期待している以上に入力させないことがあります。単に maxlength を使うよりも良いユーザーエクスペリエンスは、入力文字数のフィードバックを提供してサイズ以下でコンテンツを編集できるようにもしておくことです。 この例のひとつが、ソーシャルメディアに投稿する際の文字数制限です。これは JavaScript と maxlength を使った解決策の組み合わせ実現できます。

X(旧Twitter)にあるようなあの140文字以上入力できるけれど、140文字以上は赤字になって送信できなくなる機能のことですかね
たしかにこっちの方が「コピペ->整形->投稿」がしやすいかもしれません

#### min, max
`<input>`の`type`が`number`の際の最小値、最大値を制限するやつです
2以上10以下に設定します

```tsx
<Stack gap={4} m={8}>
  <label htmlFor="num">num:</label>
  <Input type="number" id="num" name="num" min={2} max={10}/>
  <Button type="submit">Submit</Button>
</Stack>
```

入力していない状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/1bcb8ecb-46b1-5b06-6a0d-36521db2afb8.png)

1を入力した状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/c0ce8c97-23ac-7311-9eb7-7c805b2bfb25.png)

2を入力した状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/c14ddf16-9497-e0fa-4775-52fa3547d3c6.png)

11を入力した状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/3d2cc4e6-abdc-4635-b165-da91b2df1927.png)

11を入力してsubmitした状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/8c8e6fa7-9878-48a3-79c4-068e7cd7b623.png)


なんか途中で気づいたんですけれども、マウスのホバー時もエラーが表示されています
minLentgth, maxLength, patternでも確認したら同様でした
入力した直後にも検証されているのと、submit時にも検証しており、
エラーを出力する箇所が2つあると見られますね

ちなみ`number`にすると数値を上下するカチカチが右側につくのですが、min, maxの値を超えて変化することはなさそうです ただキーボード入力だと入力できます

ちなみに3.4など、値の範囲内の数値でも小数点ありですとエラーにみなされます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/42f66d51-b38a-c2e8-6c9b-adb1d0eede12.png)

これは以下のように`step`を小数点単位にすると解消されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/e8832d28-8431-d4f0-f45d-d09065239946.png)

これは[ここ](https://developer.mozilla.org/ja/docs/Learn/Forms/Form_validation#%E5%85%A5%E5%8A%9B%E6%AC%84%E3%81%AE%E5%80%A4%E3%81%AB%E5%88%B6%E7%B4%84%E3%82%92%E5%8A%A0%E3%81%88%E3%82%8B)のメモに書いてあります

ちなみになんですが、Chakraには`<NumberInput>`というコンポーネントが用意されています
Webで確認した感じこちらは特に何もしなくても小数点も範囲内であれば良さそうに見えます
（実際に書いてないので不明瞭ではある）

### JavaScript を使用したフォーム検証

#### 制約検証API

以下があるよと紹介されています
- `HTMLButtonElement`
- `HTMLFieldSetElement`
- `HTMLInputElement`
- `HTMLOutputElement`
- `HTMLSelectElement`
- `HTMLTextAreaElement`

正確にはHTML DOM APIと呼ぶ方が正しいのでしょうか？
上記各種に以下のようなプロパティが存在してます
- `validationMessage`: エラーの時のメッセージを返す
- `validity`: 各種バリデーションチェックの状態やメッセージの更新等が出来る


詳細はこちらを読んでください
https://developer.mozilla.org/ja/docs/Learn/Forms/Form_validation#%E5%88%B6%E7%B4%84%E6%A4%9C%E8%A8%BC_api

#### エラーメッセージの書き換え
標準で出力されるエラーメッセージを変更してみます

以下がコードです
```tsx
useEffect(() => {
  const email = document.getElementById("mail") as HTMLInputElement;
  if (email) {
    email.addEventListener("input", (event) => {
      if (email.validity.typeMismatch) {
        email.setCustomValidity("メールアドレスを入力してください。");
      } else {
        email.setCustomValidity("");
      }
    });
  }
}, []);

return (
  <Form action="/sample-form" method="post">
    <Stack gap={4} m={8}>
      <label htmlFor="mail">メールアドレスを教えてください:</label>
      <Input type="email" id="mail" name="mail" />
      <Button type="submit">Submit</Button>
    </Stack>
  </Form>
);
```
（※このコードだとレンダリングのたびに`addEventListener`がはしって追加しまくるかも
unmount?のタイミングで`removeEventListener`をしたほうが良いのでしょうね クリーンアップとかって呼ばれてるやつだと思います）


標準だとこのようなメッセージですが
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/bcdce753-e372-4179-60c6-246e9440e4bb.png)

書きかえることでメッセージが書き換わります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/1553b81d-02de-b898-e10f-7e0b81e3999d.png)

これはMDNのサンプルをReact用に書き換えたのですが、メッセージを見るとホバーの時のメッセージとsubmitした後のメッセージが異なるようにみえます

`console.log`を入れてブラウザから確認してみると、メッセージは次のようになっていました
（まったくわからない）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/0d198d96-9e95-6191-75e7-7eec5d32c266.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/9f70eff0-60dc-bb41-719f-1fa78f8a43fd.png)

ちなみに公式ドキュメントもそうなってます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/c2af8c29-af95-69f7-ba5e-028804fccecd.png)

ちょっと調べたくらいだと分からなかったのでスルーします、ひとまずエラーメッセージはこのように書き換えられるということです
でもちょっとこの方法で変えるのは、美しくはないかもしれません


#### エラーメッセージを要素に出力する

```tsx
useEffect(() => {
  const form = document.querySelector("form") as HTMLFormElement;
  const email = document.getElementById("mail") as HTMLInputElement;
  const emailError = document.querySelector("#mail + span.error")!;
  
  const handleInput = (event: Event) => {
    if (email.validity.valid) {
      emailError.textContent = "";
      emailError.className = "error";
    } else {
      showError();
    }
  };
  
  const handleSubmit = (event: Event) => {
    if (!email.validity.valid) {
      showError();
      event.preventDefault();
    }
  };
  
  const showError = () => {
    if (email.validity.valueMissing) {
      emailError.textContent = "You need to enter an email address.";
    } else if (email.validity.typeMismatch) {
      emailError.textContent = "Entered value needs to be an email address.";
    } else if (email.validity.tooShort) {
      emailError.textContent = `Email should be at least ${email.minLength} characters; you entered ${email.value.length}.`;
    }
    emailError.className = "error active";
  };
  
  email.addEventListener("input", handleInput);
  form.addEventListener("submit", handleSubmit);
  
  return () => {
    email.removeEventListener("input", handleInput);
    form.removeEventListener("submit", handleSubmit);
  };
}, []);

return (
  <Form action="/sample-form" method="post" noValidate>
    <Stack gap={4} m={8}>
      <label htmlFor="mail">
        <span>メールアドレスを入力してください。</span>
        <Input type="email" id="mail" name="mail" required minLength={8} />
        <span className="error" aria-live="polite"></span>
      </label>
      <Button type="submit">Submit</Button>
    </Stack>
  </Form>
);
```

```css
input[type="email"] {
  appearance: none;

  width: 100%;
  border: 1px solid #333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  box-sizing: border-box;
}

input:invalid {
  border-color: #900;
  background-color: #fdd;
}

input:focus:invalid {
  outline: none;
}

.error {
  width: 100%;
  padding: 0;

  font-size: 80%;
  color: white;
  background-color: #900;
  border-radius: 0 0 5px 5px;

  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}
```
（※今回はクリーンアップをしてみました）

初期状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/47d0fa51-117e-6fdb-ec0d-4b5d4c452512.png)

一度入力して空にした状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/0b2c1164-45cc-9d02-af65-57df11ee97d7.png)

1文字入力した状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/994c84bd-a10c-b480-4782-2a1cdaa104e2.png)

文字数が足りない状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/ddcca5c4-088f-b0d6-84f4-0a8e688956cf.png)

制約に引っかかってない状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/de751db4-30af-2023-d203-5e8a43bd358a.png)

初期状態だと`<span>`に文字は表示されていませんが、`<input>`の背景が赤になってますね
これは`<input>`に`required`が付与されており、初期状態は入力がない(= エラー)ためcssで背景色等を変えてるからですかね
とはいえ初期状態は入力がないのは当たり前な気がするので、これは不自然とかもしれません

1つずつ見ていきます

##### `<Form>`に`noValidate`が付きました
これで`<Form>`のsubmit時の検証をOFFにしています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/c8548db9-7fe7-3ecd-0ea1-55ca5dcb7308.png)
ONにすると標準の検証が走ります（当たり前ですね）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/88b76880-c31f-ce98-8aff-a9239f691f2d.png)
OFFだとメッセージは出力されないので、この2箇所のメッセージはsubmitに関連しているということでしょうか？
ただ小さな方のメッセージがちょっとよくわかりませんね、こちらはONのときマウスカーソルをホバーするだけで表示されます うーん... やはりホバーした際のエラーメッセージの挙動がいまいちつかめませんね


##### エラーメッセージ出力用の`<span>`要素を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/8a6c9b66-af46-50d1-4330-73222faae843.png)
classに`error`と付けて、errorの要素をとれるようにすることが目的だと思います  
また、`aria-live`というオプションが付きました これは要素が更新されることを意味するそうです
https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Attributes/aria-live


##### javascriptによるエラーメッセージの出力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/0934ecb9-954d-bdde-c9a6-b582fca54200.png)
```js
const emailError = document.querySelector("#mail + span.error")!;
```
`useEffect`でイベントを追加しています
ここで該当span要素のElementを取得して、制約検証APIのプロパティを参照してそれによってtextに値をいれてる感じですね やはりちょっと面倒に感じます

標準の制約のみで対応すると細かいエラーメッセージや出力箇所をいじれず、かといっていじる際はsubmitまでの一連の流れの手順の面倒を見る必要があるように思えますね

#### 制約検証APIを使用しない例
https://developer.mozilla.org/ja/docs/Learn/Forms/Form_validation#%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF_api_%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%AA%E3%81%84%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E6%A4%9C%E8%A8%BC
この部分ですが、面倒なのでコードを実際に書いたりはしません
素直にバリデーション用のライブラリを使った方が良さそうですね

## フォームデータの送信
今回は学びたいことから逸れるので省きます
でも`action`と`method`, `enctype`を頭に入れておけば良さそうです

## 読み終えて
いろいろと考えるのが難しいので簡単にしたい気持ちがありますね
ちょっと自分の認識を言語化していきます

### 自分の考えるフォームの構成
フォームには大雑把に以下の要素を持つと認識しました
- ラベル
- 入力フィールド
- エラーメッセージ（標準の出力、もしくは自分で要素を作る）

また、Formの要素としてはfieldsetも存在していました

こんな感じでしょうか
- Field -> 単一のinput等から構成されるFormの入力要素の塊
- Fieldset -> 複数のinputから構成されるFormの入力要素（というかradio, checkbox）の塊、または任意でグループ化したい場合

以下のような形です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/6ddd3c96-d67e-64d6-b3c7-c69f2b846040.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/059cafca-9c11-fa29-b5c8-5cee186abe5e.png)


こうするとこんな感じの実装をしておけば間違いないでしょうか
```tsx
<Form action="/sample-form" method="post">
  <Stack gap={4} m={8}>
    <label htmlFor="name">
      <Text>
        Name:
        <Mark aria-label="required" variant={"plain"} fontWeight={"bold"}>
          *
        </Mark>
      </Text>
    </label>
    <Input type="text" id="name" name="username" required />
    
    <fieldset>
      <legend>Title</legend>
      <RadioGroup name="title">
        <HStack gap={4}>
          <Radio id="title_1" value="A">
            Ace
          </Radio>
          <Radio id="title_2" value="K">
            King
          </Radio>
          <Radio id="title_3" value="Q">
            Queen
          </Radio>
        </HStack>
      </RadioGroup>
    </fieldset>
    <Button type="submit">submit</Button>
  </Stack>
</Form>
```
今までの章から切り貼りしただけですが、シンプルなFiledとFieldsetの想定です
これでフォームの最小限を持ってると言えます
ただこれですと、エラーメッセージは標準ですしユーザーには優しくないかもしれませんね

### ChakraUIを使用してFormを作る

ChakraUIでこんなものを見つけました
https://www.chakra-ui.com/docs/components/field
https://www.chakra-ui.com/docs/components/fieldset

書き換えてみましょう
```tsx
<Form action="/sample-form" method="post">
  <Stack gap={4} m={8}>
    <Field label={"name"} required>
      <Input type="text" name="username" />
    </Field>
    
    <Fieldset.Root>
      <Fieldset.Legend>Title</Fieldset.Legend>
      <Fieldset.Content>
        <RadioGroup name="title">
          <HStack gap={4}>
            <Radio value="A">
              Ace
            </Radio>
            <Radio value="K">
              King
            </Radio>
            <Radio value="Q">
              Queen
            </Radio>
          </HStack>
        </RadioGroup>
      </Fieldset.Content>
    </Fieldset.Root>
    <Button type="submit">submit</Button>
  </Stack>
</Form>
```
ちょっとすっきりしました
見た目も悪くはないです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/46b4633f-83d7-522b-13c4-1cd24c143b45.png)

開発者ツールからHTMLを確認してみます

`<label>`の`for`と`<input>`の`id`が何もしなくても同一になっています
さらに`required`を`<Field>`につけたら`<input>`へ反映されていますね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/3e75c9fe-fc35-bf18-1a13-45c2a5224eb5.png)

必須の際は`aria-label`で音を書き換えてましたが、こちらは`aria-hidden`を使ってそもそも隠してるようです
ここは用途によって分かれそうです でも変な挙動でもないのでこれはこれでアリな気がしています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/b530a8d1-6966-39ba-b22d-8c3221b07bfd.png)
https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Attributes/aria-hidden

`<Feildset>`のラジオボタンも`<label>`と`<input>`の`for`と`id`が一致しています
これは`<Radio>`の力でしょうね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/f99f7283-5a44-0887-f3f4-761d7b7aa947.png)

### エラーメッセージを考える

このままだとエラーメッセージは標準のままですが、この`<Field>`は`errorText`があれば出力するようになっています
こんな感じで無理やり出力させてみます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/fd804afb-74d0-ecb6-3070-b21f39befcb6.png)

ちゃんと出てきました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/b9c9148f-5808-47b7-ab96-24fe25e25127.png)
さらには`aria-live`まで設定されています、このMDNのFormに出てきたaria-hogeというモノに関しては網羅できていそうですね さらにはちゃんと赤枠になっており、cssを改めて記述する必要がなくなりました

エラーメッセージもちゃんと変えられるようにしましょう
ただ素直にJavaScriptを書いていくのはちょっと憚られるので、便利なFormライブラリとして[Conform](https://ja.conform.guide/)を使用します


書き換えたものがこちらです
```tsx
// zodで定義したスキーマ ここで検証を設定
const schema = z.object({
  name: z.string({ required_error: "必須です-1" }),
  title: z.string({ required_error: "必須です-2" }),
});

// Hooks これで各種属性の設定や検証用のjavascriptを生成してるんじゃないかなって思った
const [form, fields] = useForm({
  constraint: getZodConstraint(schema),
  shouldRevalidate: "onInput",
  onValidate: ({ formData }) => parseWithZod(formData, { schema }),
});

// 本体 Conformの公式をみて書いた fieldsetの書き方はちょっとあってるかいまいちだけど
<Form action="/sample-form" method="post" {...getFormProps(form)}>
  <Stack gap={4} m={8}>
    <Field label={"name"} required invalid={!fields.name.valid} errorText={fields.name.errors}>
      <Input {...getInputProps(fields.name, { type: "text" })} />
    </Field>
    
    <Fieldset.Root invalid={!fields.title.valid} {...getFieldsetProps(fields.title)}>
      <Fieldset.Legend>Title</Fieldset.Legend>
      <Fieldset.Content>
        <RadioGroup name="title">
          <HStack gap={4}>
            <Radio value="A" invalid={!fields.title.valid}>Ace</Radio>
            <Radio value="K" invalid={!fields.title.valid}>King</Radio>
            <Radio value="Q" invalid={!fields.title.valid}>Queen</Radio>
          </HStack>
        </RadioGroup>
      </Fieldset.Content>
      <Fieldset.ErrorText>{fields.title.errors}</Fieldset.ErrorText>
    </Fieldset.Root>
    <Button type="submit">submit</Button>
  </Stack>
</Form>
```


バリデーションもちゃんと問題なさそうで、エラーの文言も変更できています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/8309592f-a148-3c56-235b-c7e82fe1ef3f.png)
またマウスカーソルをホバーしたときのエラー、submitしたときに出るエラーの2種類でることもなくなって良くなっています これは`Form`に設定しているgetFormPropsの返り値に`noValidation: true`が存在してるからですね

組み込みの検証についてもzodのスキーマ定義で任せてしまえば良さそうです


### 最後にまた確認
一応開発者ツールからHTMLを確認してみます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/c0916850-472c-1061-2447-61a2bbfd753e.png)
`<Field>`の方は`<label>`の`for`と`<input>`の`id`が異なってますね
これはConformのヘルパーで`<input>`の`id`を上書きしちゃってるからでしょうか

ここは実際使うときにはChakraの<Field>を加工する必要がありそうです
スニペットをいじるだけなのでたいした労力ではないと思います

多少いじることはあるとはいえ、ライブラリの力を借りるととんでもなくありがたみを感じますね
よっぽどのことがない限りはUIライブラリやFormのライブラリを公式ドキュメントをよく読んで、
最終確認で開発者ツール等にてHTMLを一応見ておくってのが良さそうな感じでしょうか
特に複数のライブラリ（UIライブラリとFormのライブラリとか）を組み合わせて作るときはちゃんとHTMLを見ておいた方が良いよってとこですね

ちなみにConformのヘルパーからは以下のそれっぽいものが渡されます
- `aria-invalid`
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-invalid
- `aria-describedby`
https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Attributes/aria-describedby

たびたびaria-hogeみたいなものが登場していましたが、これらはWAI-ARIAと呼ばれるものです
https://developer.mozilla.org/ja/docs/Learn/Accessibility/WAI-ARIA_basics
（読み方は「うぇいありあ」っぽいです）

アクセシビリティのためにHTML要素へ追加で意味を提供する一連の属性、という認識をしました
MDNにも記載されていますが、「WAI-ARIAは必要な場合のみ使用する」が重要っぽいです
基本はHTMLの要素を適切に使うことが優先でしょうね


アクセシビリティの該当欄にも以下のように記載されています
> 既定では、HTML は正しく使用すればアクセシブルです。

https://developer.mozilla.org/ja/docs/Learn/Accessibility

ではなぜ`aria-invalid`と`aria-describedby`はConformのヘルパーから返されるのでしょうね？（設定で返さなくするようにも可能）

どうやらここで一応会話されてるようです、もともとは標準ではOFFだったようです
https://github.com/edmundhung/conform/issues/213

`aria-invalid`は状態を説明する属性であり、標準では状態をもっていないので追加しているといった感じでしょうか？

`aria-describedby`は何かと関連付ける属性であり、ConfromではerrorIdと紐づいてます

（↓こんな感じです、getInputPropsからerrorIdが込められた`aria-describedby`が渡される）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/37b9abb0-d3ff-a8bd-ed75-7a4aa9fff0de.png)
このスクリーンショットはConformのチュートリアルから拝借しました
https://ja.conform.guide/tutorial

エラーメッセージの`id`と`<input>`の`aria-describedby`が同一の`id`なので紐づいています
且つ、この`aria-describedby`は入力でエラーが発生したときのみConformのヘルパーから返ってきます
なのでエラーが発生してメッセージが出力されたら紐づく、みたいな感じですかね
たしかに標準のHTMLだけではちょっと表現が難しいのかもしれません

---

ちょっと見返すと、先ほど書いたコードもエラーの時の`<input>`の`aria-describedby`とメッセージを出力している`<span>`の`id`が異なっています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/f1b8b2b5-8742-7d26-c096-051032725b5b.png)
まぁ`aria-describedby`の値はConformのfield.hoge.errorIdとなるのですが、これを`<input>`にしか渡していないせいですね
ここを加工すれば一致させられるかと思います、たいした労力ではないです

こんな感じでエラーの際のイメージを更新します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/eb9d4459-5bf2-ca95-baad-d51958078298.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468047/58de7030-62fe-3df8-c8ad-8111c4d7c221.png)


ちなみに前回書いた記事で`<Field>`を加工した`<ConformFiled>`は`<label>`の`for`と`<ErrorText>`の`id`を受け取れるようになってるので対応できてそうです
https://qiita.com/stopod/items/42ac91a67037e849e97f#%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E7%94%BB%E9%9D%A2%E3%81%AE%E4%BD%9C%E6%88%90



よくわからなくても公式ドキュメントを真似ておくのは良かったみたいです
（ちゃんと理解してから真似るべきではありますが、自戒です）

## 終わりに
知らないことがてんこ盛りでした
他にも付けておいた方が良いアクセシビリティがあるのかもしれませんが、適宜考えて使っていくことにします

ライブラリの力は偉大ですが、使わずに進めてライブラリはどうやって実現させてるのかを想像するのはためになりました

なんか読み返すとChakraとConformの回し者みたいになった気がします
他のライブラリを使う際も同様に確認しながら進めたいと思います

検証で使ったリポジトリはここです
https://github.com/stopod/rr-chakra-conform-sample/blob/form-test/app/routes/sample-form.tsx