# フォーム

> data-sitekit-form を付けるだけのお問い合わせフォーム。表示・通知・自動返信・テスト送信・流入元の記録。

URL: https://sitekit.example/docs/forms

## 最小の形

項目だけ書きます。送信処理・hidden 項目・エラー表示の JavaScript は要りません（[script タグ](https://sitekit.example/docs/script) は入れておきます）。

```html
<form data-sitekit-form="contact">
  <label>お名前 <input name="name" required autocomplete="name"></label>
  <label>メール <input type="email" name="email" required autocomplete="email"></label>
  <label>お問い合わせ内容 <textarea name="message"></textarea></label>

  <!-- ボット対策。人には見えない場所に置く（必須） -->
  <input type="text" name="_hp" tabindex="-1" autocomplete="off"
         style="position:absolute;left:-9999px" aria-hidden="true">

  <button type="submit">送信する</button>
</form>

<div data-sitekit-success role="status" aria-live="polite" hidden>お問い合わせありがとうございました。</div>
<div data-sitekit-error role="status" aria-live="polite" hidden></div>
```

## 名前を合わせる

| HTML | schema.json |
|---|---|
| `data-sitekit-form="contact"` の `contact` | `forms[].key` |
| 入力の `name="email"` | その form の `fields[].key` |

- schema.json に無いフォームのキーは `not_found` になります
- schema.json に無い `name` はエラーにならず、保存もされません
- `required` の項目は、空だと `invalid_request` になります（項目ごとの文字数などの制約は [schema.json の型](https://sitekit.example/docs/schema#型)）

## ハニーポット（`_hp`）は必ず入れる

`name="_hp"` の入力は、人には見えないボット対策の欄です。**ここが埋まっている送信は保存されません**。ただしボットに気づかせないため、画面上は成功と同じに見えます。

- `tabindex="-1"` と `autocomplete="off"` を付け、画面の外に置きます（ブラウザの自動入力で埋まらないように）
- schema.json には書きません

## 成功・エラーの表示

`[data-sitekit-success]` と `[data-sitekit-error]` は、**フォームの中か、フォームと同じ親要素の直下**に置きます。

- 成功するとフォームが `hidden` になります。**成功の文言はフォームの外に置きます**（中に置くと一緒に隠れる）
- `role="status" aria-live="polite"` を付けます。付けないと、送信後に出た文言がスクリーンリーダーに読み上げられません
- エラーのときは `[data-sitekit-error]` に次の文言が入ります

| 状況 | 表示される文言 |
|---|---|
| 入力の誤り（`invalid_request`） | 入力内容をご確認ください |
| 短時間に送りすぎ（`rate_limited`） | しばらく経ってからお試しください |
| Turnstile の確認に失敗（`turnstile_failed`） | 確認に失敗しました。もう一度お試しください |
| 通信できない | 送信できませんでした。通信環境をご確認ください |
| そのほか | 送信できませんでした。時間をおいてお試しください |

入力の誤りのときは、該当する項目に `aria-invalid="true"` が付きます。見た目はサイト側の CSS で当てます。

```css
[aria-invalid="true"] { border-color: #c00; }
```

## 送信後に別ページへ移動する

```html
<form data-sitekit-form="contact" data-success-url="/thanks/">
```

**同じオリジンのページだけ**です。別のオリジンへ移動するときは、明示して許可します。

```html
<form data-sitekit-form="reserve"
      data-success-url="https://booking.example.com/done"
      data-success-external="1">
```

スキームやポートが違えば別オリジンです。`javascript:` と `data:` は `data-success-external` を付けても移動しません。

## 二重送信の防止

連打や、入力欄での Enter による再送信は自動で止めます。送信中は送信ボタンが `disabled` になり、`<form>` に `aria-busy="true"` が付きます。`form="フォームのid"` でフォームの外に置いた送信ボタンにも効きます。

## 複数の値を持つ項目は使えない

チェックボックスの群や `<select multiple>` など、**同じ `name` を複数使うと最後の値だけ**が送られます。複数の答えが要る質問は、キーを分けます。

```html
<!-- NG: topic が1つしか届かない -->
<input type="checkbox" name="topic" value="a">
<input type="checkbox" name="topic" value="b">

<!-- OK -->
<input type="checkbox" name="topic_a" value="1">
<input type="checkbox" name="topic_b" value="1">
```

## Turnstile

Cloudflare Turnstile のウィジェットが作る `cf-turnstile-response` の入力があれば、自動で一緒に送ります。サイト側で特別な記述は要りません。

## 通知

お問い合わせは**保存された時点で確定**します。通知はそのあとに送られ、どの通知が失敗してもお問い合わせは消えません。各通知の成否は管理画面のお問い合わせの詳細に出て、失敗したものは再送できます。

通知先は schema.json ではなく、**管理画面の「設定」→ 各フォーム**で、サイトのオーナーが変えます。

| 通知 | 設定するもの |
|---|---|
| お店へのメール | お知らせ先のメールアドレス（複数可） |
| Slack | Incoming Webhook の URL（Slack でアプリを作り、投稿先チャンネルを選んで発行する） |
| スプレッドシートへの記録 （近日）| 外部のアカウントへの開放は準備中です |

- 通知ごとにオン・オフを切り替えられます
- Slack の Webhook URL は、持っている人が誰でもそのチャンネルに投稿できる鍵です。管理画面は伏せ字で表示し、API も返しません。URL を失くしたら Slack で発行し直します
- 設定を変えられるのはオーナーだけです（スタッフは見るだけ）

## お礼メール（自動返信）と差し込み変数

お客様へのお礼メールは、次のときに送られます。

1. 管理画面の設定で、そのフォームの「お礼メール」がオン
2. そのフォームに `type: "email"` の項目があり、値が入っている

件名と本文は設定画面で書きます（プレビューあり）。差し込める変数:

| 変数 | 中身 |
|---|---|
| `{{name}}` `{{email}}` など | フォームの項目の値（項目の key そのまま） |
| `{{site_name}}` | サイト名 |
| `{{form_label}}` | フォームの名前（`forms[].label`） |
| `{{submitted_at}}` | 送信日時 |

```text
{{name}} 様

{{site_name}} へのお問い合わせありがとうございます。
内容を確認のうえ、ご連絡いたします。

受付日時: {{submitted_at}}
```

## 送信を GTM で拾う

送信が**成功したときだけ**、`dataLayer` に次が積まれます。

```js
{ event: "sitekit_form_submit", form: "contact" }
```

- `form` は `data-sitekit-form` に書いたキーです
- イベント名は管理画面の「設定 → 各フォーム → 完了イベント名」で変えられます。サイトの再デプロイは要りません。**名前を変えたら GTM のトリガーも直します**（古い名前のトリガーは黙って動かなくなります）
- ハニーポットで弾いた送信も画面上は成功なので、このイベントは積まれます。実際に届いた件数は管理画面の一覧で数えます

GTM 側は、トリガー「カスタム イベント」のイベント名に上の名前を入れ、フォームで分けたいときはデータレイヤーの変数 `form` を条件にします。

## テスト送信

いちばん確かなのは、公開したページ（開発用ドメインでもよい）から実際に送ることです。API に直接送ることもできます。

```bash
curl -s -X POST -H 'content-type: application/json' \
  -d '{"name":"テスト","email":"test@example.com","message":"テスト送信です"}' \
  https://cdn.sitekit.example/v1/sites/hanaya/forms/contact
# => {"ok":true,"id":"...","gtm_event":"sitekit_form_submit"}
```

**テストでも本物のお問い合わせとして保存され、通知も届きます。** 件数の上限にも数えられます。試したあとは管理画面から消してください。CLI の `forms test` は準備中です （近日）。

## 流入元（UTM）の記録

script が覚えておいた流入元（最初に来たときと最後に来たときの `utm_*` / `gclid` / `fbclid`・着地ページ・参照元）が、送信のたびに自動で添えられ、お問い合わせと一緒に保存されます。サイト側の作業はありません（[script タグ](https://sitekit.example/docs/script#流入元utmの記録)）。

## 受付の上限

フォームの受付件数はプランごとに上限があります（Dev は通算、有料プランは月ごと）。上限に達したときの動きは [プランと上限](https://sitekit.example/docs/plans#上限に達したとき) にあります。
