ガイド / アクセシビリティ

アクセシビリティ

フォームをアクセシブルにするには、各フォーム要素を適切な属性で設定する必要がありますが、 Conform がその手助けをします。

#Aria 属性

アクセシビリティに関しては、通常、異なる要素を関連付けるために一意の ID が必要になる Aria 属性が最初に思い浮かびます。Conform は、必要なすべての ID を生成してくれることで、この点でのサポートを提供します。

1import { useForm } from '@conform-to/react';
2
3function Example() {
4  const [form, fields] = useForm();
5
6  return (
7    <form id={form.id}>
8      <label htmlFor={fields.message.id}>Message</label>
9      <input
10        type="text"
11        id={fields.message.id}
12        name={fields.message.name}
13        aria-invalid={!fields.message.valid ? true : undefined}
14        aria-describedby={
15          !fields.message.valid
16            ? `${fields.message.errorId} ${fields.message.descriptionId}`
17            : fields.message.descriptionId
18        }
19      />
20      <div id={fields.message.descriptionId}>The message you want to send</div>
21      <div id={fields.message.errorId}>{fields.message.errors}</div>
22      <button>Send</button>
23    </form>
24  );
25}

#バリデーション属性

バリデーション属性も、スクリーンリーダーのヒントを改善するなど、アクセシビリティにおいて重要な役割を果たします。 Conform を使用すると、 zod や yup スキーマからバリデーション属性を導出し、各フィールドのメタデータにそれらを反映させることができます。

1import { parseWithZod, getZodConstraint } from '@conform-to/zod';
2import { useForm } from '@conform-to/react';
3import { z } from 'zod';
4
5const schema = z.object({
6  message: z
7    .string()
8    .min(10)
9    .max(100)
10    .regex(/^[A-Za-z0-9 ]{10-100}$/),
11});
12
13function Example() {
14  const [form, fields] = useForm({
15    constraint: getZodConstraint(schema),
16    onValidate({ formData }) {
17      return parseWithZod(formData, { schema });
18    },
19  });
20
21  return (
22    <form id={form.id}>
23      <input
24        type="text"
25        name={fields.message.name}
26        required={fields.message.required}
27        minLength={fields.message.minLength}
28        maxLength={fields.message.maxLength}
29        pattern={fields.message.pattern}
30      />
31      <button>Send</button>
32    </form>
33  );
34}

#プログレッシブエンハンスメント

プログレッシブエンハンスメントも、一時的なネットワークの問題の影響を最小限に抑えるなど、アクセシビリティを支援します。たとえば、 Conform を使用すると、ページのリフレッシュをまたいでもフォームデータと状態が保持されるように、フィールドリストを操作できます。

1import { useForm } from '@conform-to/react';
2
3export default function Example() {
4  const [form, fields] = useForm();
5
6  return (
7    <form id={form.id}>
8      <ul>
9        {tasks.map((task) => (
10          <li key={task.key}>
11            <input name={task.name} defaultValue={task.initialValue} />
12            <button
13              {...form.remove.getButtonProps({
14                name: fields.tasks.name,
15                index,
16              })}
17            >
18              Delete
19            </button>
20          </li>
21        ))}
22      </ul>
23      <button
24        {...form.insert.getButtonProps({
25          name: fields.tasks.name,
26        })}
27      >
28        Add task
29      </button>
30      <button>Save</button>
31    </form>
32  );
33}

#ボイラープレートの削減

上記で述べたすべての属性を設定することは、面倒でエラーが発生しやすい作業です。 Conform は 、関連するすべての属性を導出する一連のヘルパーを提供することで、この作業を支援しようとしています。

注意: これらすべてのヘルパーはネイティブ HTML 要素用に設計されています。 react-aria-components や Radix UI のようなカスタム UI コンポーネントを使用している場合、それらの API を通じて既に属性が設定されている可能性があるため、これらのヘルパーが不要になるかもしれません。

以下は、手動設定と比較した場合の例です。ヘルパーについて詳しく知りたい場合は、上記リンクの対応するドキュメントを確認してください。

1import { parseWithZod, getZodConstraint } from '@conform-to/zod';
2import { useForm } from '@conform-to/react';
3import { z } from 'zod';
4
5const schema = z.object({
6  message: z
7    .string()
8    .min(10)
9    .max(100)
10    .regex(/^[A-Za-z0-9 ]{10-100}$/),
11});
12
13function Example() {
14  const [form, fields] = useForm({
15    constraint: getZodConstraint(schema),
16    onValidate({ formData }) {
17      return parseWithZod(formData, { schema });
18    },
19  });
20
21  return (
22    <form id={form.id}>
23      {/* ビフォー */}
24      <input
25        type="text"
26        id={fields.message.id}
27        name={fields.message.name}
28        required={fields.message.required}
29        minLength={fields.message.minLength}
30        maxLength={fields.message.maxLength}
31        pattern={fields.message.pattern}
32        aria-invalid={!fields.message.valid ? true : undefined}
33        aria-describedby={
34          !fields.message.valid
35            ? `${fields.message.errorId} ${fields.message.descriptionId}`
36            : fields.message.descriptionId
37        }
38      />
39      {/* アフター */}
40      <input
41        {...getInputProps(fields.message, {
42          type: 'text',
43          ariaDescribedBy: fields.message.descriptionId,
44        })}
45      />
46      <button>Send</button>
47    </form>
48  );
49}