@200stack/static-forms-astro
v0.2.4
Published
Astro Static Forms adapter for 200stack build-time materialization.
Readme
Static Forms Astro adapter
checkboxes(複数選択)は 0.2.3 以上(core は 0.2.2 以上)・build protocol v2 が必要です。
電話番号 tel・URL url・日付 date・数値 number は 0.2.4 以上(core も 0.2.4 以上)・build protocol v2 が必要です。
v2 と開発表示は slotId だけで描画できます。slotId は、200stack のフォームの画面の「サイトに貼るコード」に入っている名前(コードに書く名前)です。本番の v2 は core が作った HTML をそのままページに埋め込みます。
---
import StaticForm from '@200stack/static-forms-astro/StaticForm.astro'
---
<StaticForm slotId="slot.contact" />同じフォーム(コードに書く名前が同じもの)を1ページに複数置く場合は、2つ目以降に index={1}、index={2} のように異なる値を渡してください。既定は 0 です。同じ instanceId の2つ目以降は診断を表示し、送信の controller を起動しません。生成される ID は固定の CSS セレクタとして扱わないでください。
v2 と開発表示の言語はフォームの定義の lang (ja・en) に従います。言語ごとに別のフォームを作り、それぞれのページに置いてください。通常、コンポーネントへの lang 指定は不要です。定義に lang がないフォームだけ、lang="en" などで既定の文言を選べます。どちらも省略なら日本語です。定義と異なる lang を指定すると、本番ビルド・開発表示ともエラーになります。同じ言語の明示指定は省略と同じ結果です。integration の options に lang の既定はなく、コンポーネントへ渡します。
送信・戻る・確認の送信・noscript・状態・項目エラーと開発の案内は、解決した言語の辞書を使います。保存された項目名や submitLabel・backLabel などの文言は自動翻訳しません。backLabel は確認の mode: 'configured' で省略可能です。本番・開発のフォームルートには HTML の lang と既存の data-200stack-static-form-lang を出します。ページ全体の <html lang> はサイト側で指定してください。データ取得に失敗して定義が分からない開発診断は、コンポーネントの lang、省略なら日本語を使います。v1 の出力は従来の英語固定で、定義・adapter の lang を適用しません。StaticFormLang 型を adapter から import できます。
0.1.0 は 0.2.0 のビルド入力を読めません。新しい定義・入力を使うサイトは adapter と core・SDK を対応する版へ更新してください。
言語を足す手順
- core の
src/core/messages.tsにフォーム文言・状態・検証エラー・開発診断をすべて持つ辞書を足し、staticFormLanguageNamesに表示名を足します。StaticFormLangと wire schema の許可値は辞書のキーから導かれます。実行時の登録はしません。 - 管理画面の自動返信の固定文(件名・本文・HTML の
langとフォント)と、作成時の既定定義・言語選択を足します。 - core の dist と controller bundle、Worker の同梱 controllers、adapter の controller contract、agent の生成物を作り直し、browser import 境界・bundle 予算・core と adapters のテストを確認します。言語追加には controller の再配備も必要です。
- DB(管理画面) → Worker(新しい core と controller) → build-runtime → 管理画面 → npm の順で配備し、サイトを再ビルドします。
開発表示の重複診断には CSS の :has() を使い、JS は実行しません。:has() と埋め込みの <style> が使えるブラウザ・CSP が必要です。
route・outputPath は v1 入力でのみ必須です。v2 では使いませんが、渡された値は検証します。
v2 の StaticForm.astro は、固定入力の publicApiOrigin にある /v1/controller/astro-controller.<sha256>.js を module script として参照します。controller はサイトの出力へコピーしません。hash は同梱 bundle の SHA-256 全64桁です。
CSP を使うサイトは script-src に Static Forms の配信元ドメインを追加してください。
初版では Astro の ClientRouter によるページの差し替えに対応しません。フォームを含むページへの移動は通常のページ読み込みを使ってください。
ボット対策は、送信の前にブラウザの中で行う計算の確認(ALTCHA と互換の proof-of-work)です。controller が自分で解くので、外部のスクリプトや iframe を読まず、CSP に追加の配信元は要りません。
リリース時に scripts/build-controller.mjs が bundle・controller contract・contracts/controller-manifest.v2.json を生成します。manifest は { name, hash, file } の配列で、file はこのパッケージのルートからの相対パスです。配信側は各版の bundle を対応する URL へ配置し、公開済みの版を保持してください。
見た目を指定する(v2)
---
import StaticForm from '@200stack/static-forms-astro/StaticForm.astro'
import type { StaticFormStyle } from '@200stack/static-forms-astro'
const style = {
section: { title: 'text-xl font-bold' },
field: { root: 'mb-5', label: 'font-medium', error: 'text-rose-600' },
email: { control: 'rounded border aria-[invalid=true]:border-rose-600' },
radio: { group: 'grid gap-2', option: 'flex gap-2', input: 'size-4' },
byName: { email: { label: 'text-indigo-800' } },
} satisfies StaticFormStyle
---
<StaticForm slotId="slot.contact" style={style} />値はクラス名の文字列だけです。項目は field → 種類 (text / email / textarea / select / radio / checkbox) → byName[name] の順に連結します。この順は CSS の優先順位を保証しません。未知の byName、またはその項目の種類に合わない部品は警告し、後者は使いません。
style と lang はサーバーで HTML に反映し、v2 の client props には含めません。HTML の SHA-256 である semanticDomDigest が remount の key であり、HTML が変わると入力・フォーカス・送信状態をリセットします。エラーの入力は aria-invalid="true"、項目外枠は data-200stack-static-form-invalid、送信中の root は aria-busy="true" です。checkbox は consentText があれば fieldset > legend + label > input、select は先頭に空の選択肢を出し、確認画面は configured・generated とも入力内容を表示します。固定 CSS は nonce を受け取らないため CSP hash を使います。
| キー | DOM (part) |
| --- | --- |
| root / form | ルートの section / 内側の form |
| section.root / .title / .description | section / h2 / p |
| field.root、*.label / .description / .error | 項目 div、label または legend、説明 p、エラー p |
| text・email・textarea・select の .control | input / textarea / select |
| radio・checkbox の .group / .option / .input | fieldset / 選択肢 label / input |
| formErrors / status / submit | 全体エラー / 状態 / 主送信 button |
| privacy.root / .link | 案内 p / a |
| confirmation.* | div / h2 / p / dl / div / dt / dd / buttons |
| completion.* | 完了の div / h2 / p |
| style のキー | part の正確な値 | 本番の DOM | 開発の表示 |
| --- | --- | --- | --- |
| root / form | root / form | section / form | 外側の div[role=form] (root form) |
| section.* | section / section-title / section-description | section / h2 / p | 同じ |
| field.root / *.label / .description / .error | field / label / description / error | 項目 div / label または legend / p / p | 同じ |
| text 系 .control | control | input / textarea / select | 同じ |
| choice .group / .option / .input | group / option / input | fieldset / label / input | 同じ |
| formErrors / status / submit | form-errors / status / submit | div / div / button | 出ない / 出ない / 無効の button |
| privacy.* | privacy / privacy-link | p / a | 同じ |
| confirmation.* | confirmation、confirmation-title、confirmation-description、confirmation-list、confirmation-item、confirmation-label、confirmation-value、confirmation-back、confirmation-send | div / h2 / p / dl / div / dt / dd / buttons | 出ない |
| completion.* | completion / completion-title / completion-body | div / h2 / p | 出ない |
条件で出ない説明・確認・完了は指定しても効果がありません。consentText が無い checkbox には group と label がありません。theme と公開目印は v2 と開発表示だけで、v1 の出力には効きません。制御文字、JSON 全体 32 KiB、合成済み class 属性 2,000 バイトを超えるとビルドエラーです。開発表示も同じ検査・クラスを使います。Tailwind は完全なクラス名をソースに書き、v3 の content または v4 の @source にそのファイルを含めます。動的なクラスは検出されず、エラー用には aria-[invalid=true]:border-rose-600 のように書きます。
CSS の目印と theme.css
v2 本番と開発表示では各部品に data-200stack-static-form-part が付きます。複数の役割は空白区切りなので [data-200stack-static-form-part~="form"] のように ~= を使います。公開する目印は、部品名の data-200stack-static-form-part、項目外枠の data-200stack-static-form-type・-name・-required・-invalid です。name には .・: が入り得るため値を引用符で囲みます。name を変更すると name を狙った CSS と byName は黙って外れます。controller 実行時の要素と開発表示の説明文は公開契約外です。HTML は項目ごとに 100〜200 バイト、選択肢ごとに約 70 バイト増えます。
---
import '@200stack/static-forms-astro/theme.css'
import '../styles/static-form.css'
---@layer static-forms;
[data-200stack-static-form-part~="root"] { --200stack-sf-accent: #0f766e; --200stack-sf-accent-hover: #115e59; --200stack-sf-text: #ecfdf5; --200stack-sf-muted: #a7f3d0; --200stack-sf-border: #2d6a5c; --200stack-sf-radius: 14px; --200stack-sf-surface: #06251f; --200stack-sf-error: #fb7185; --200stack-sf-error-surface: #4c0519; }一般のサイトでは CSS の先頭に @layer static-forms; を置きます。レイヤー外の CSS と、後ろのレイヤーの CSS が勝ちます。Tailwind v4 では @layer theme, base, static-forms, components, utilities; を @import "tailwindcss"; より前に置き、@import '@200stack/static-forms-astro/theme.css'; と layer() なしで読み込みます。Astro は既定の build.inlineStylesheets: 'auto' で小さな CSS を inline にすることがあります。厳しい CSP では 'never' にしてください。
theme.css だけの本番は外部 CSS なので style-src 'self'(または配信元)で足りますが、v2 は固定の <style> を常に出します。本番は 'sha256-LE3mSYpIJxrT67iqOfLXzAcqron3YCrRA+rogsSVPYA='、開発表示は 'sha256-PooA9aoFhckRsEL2CK2Uxp0tODRwW2JydE6jQ+WgQ+M=' を style-src(指定時は style-src-elem にも)へ追加してください。開発表示の重複診断 style は出力ごとに hash が必要です。クラス指定を使う本番でも固定 style の hash は必要です。
0.1 からの移行
| 0.1 | 0.2 |
| --- | --- |
| 平たい label / description / error | field.label / .description / .error |
| input / textarea / select | 種類ごとの .control |
| fields.* / types.* の input | text 系は .control、radio・checkbox は .input |
| 文字列の field / section / confirmation / completion | それぞれの .root |
| choiceGroup / choiceLabel / radio / checkbox | radio / checkbox の .group / .option / .input |
| sectionTitle / sectionDescription | section.title / .description |
| confirmationList など | confirmation.list など |
| types / fields | 種類キー / byName |
| first / last | section 内の表示位置は CSS、特定項目は byName |
StaticFormElementClasses・StaticFormFieldClasses は削除済みで、項目の型は StaticFormFieldStyle です。旧キーは読み替えず、Static Forms style uses 0.1 key at fields.email; see README: Migrating from 0.1 のようにエラーになります。
送信の完了と計測
完了時はメッセージを表示するか、サイトのルートからのパス(例 /contact/thanks?from=contact)へ移動します。移動先は 2,048 字以内で、/ から始まる必要があります。//、絶対 URL、バックスラッシュ、空白・制御文字(0x00〜0x20、0x7f〜0x9f、U+2028・U+2029)、#、パスの .. 区間、エンコードされた .・/・バックスラッシュ(%2e・%2f・%5c、大文字も含む)は拒否します。クエリは使えます。移動先はページの URL を基準に解決し、同じ origin であることを確認します。
サンクスページはサイト側で用意してください。末尾のスラッシュはサイトのルーティングに合わせ、検索に載せたくない場合は noindex を設定してください。そのページの計測タグは、ページを直接開いた場合にも動きます。移動中は「移動しています…」(en: Redirecting…)を表示し、送信ボタンは無効のままです。移動の呼び出しが失敗した場合は、送信失敗とは区別した案内と手動リンクを表示します。location.assign を使うため、戻るボタンで入力済みの完了状態に戻れます(bfcache で保持された場合)。
受付が確認できた時点で、ルートから 200stack-static-form:accepted を通知します。bubbles: true・cancelable: false の CustomEvent です。message では完了要素を表示した後、redirect では移動を呼ぶ前に通知します。直接受付とポーリングの両方が対象です。確認できずにポーリングが時間切れになった場合や dispose 後には通知しません。
detail は通知ごとに作る凍結した { slotId?: string, publicFormId: string, instanceId: string } です。入力値・受付 ID・receipt・session は含みません。v2 の HTML には data-200stack-static-form-slot があり、SDK が渡した slotId を通知できます。属性がない旧い HTML では slotId のキーを省略します。v1 と開発表示にはこの属性を出しません。回数の保証は「同じ controller の同じ送信で最大 1 回」です。再読み込みや remount をまたぐ 1 回だけの保証はありません。イベントは偽造できるので、課金・権限付与などの受付証明には使えません。
STATIC_FORM_ACCEPTED_EVENT と StaticFormAcceptedEventDetail は core と adapter から export します。ブラウザで定数を使う場合は @200stack/static-forms-core/browser を使い、adapter の入口からは型だけを import してください。リスナーの Promise は待たず、通常の DOM リスナーの例外はブラウザが報告します。通知をキャンセルして完了処理を止めることはできません。
GTM と GA4 の例は どちらか一方 を使ってください。リスナーはサイト全体で 1 個とし、必要なら slotId で対象を絞ります。redirect で同じ成果をイベントとサンクスページの両方で数えないでください。移動前の通知は計測先への配送完了を保証しません。
計測は、サイトの Cookie の同意の管理(GTM・GA4 の同意モードなど)に従って送ってください。EU などでは計測に同意が要るのが一般的です。例は入力値を送りません。入力値を計測に足さないでください。
共有 layout の通常の <script> に一度置きます。Astro はこのスクリプトを処理して外部ファイルにまとめます。ClientRouter には引き続き対応しません。次は GTM の例です。
<script>
import type { StaticFormAcceptedEventDetail } from '@200stack/static-forms-astro'
const analytics = window as Window & {
dataLayer?: Array<Record<string, unknown>>
gtag?: (...args: unknown[]) => void
}
document.addEventListener('200stack-static-form:accepted', (event) => {
const { slotId } = (event as CustomEvent<StaticFormAcceptedEventDetail>).detail
if (slotId !== 'slot.contact') return
analytics.dataLayer = analytics.dataLayer || []
analytics.dataLayer.push({ event: 'static_form_accepted', form_slot: slotId })
})
</script>GTM ではカスタムイベント static_form_accepted をトリガーにし、GA4 イベントタグの名前を generate_lead、パラメーター form_slot を同名のデータレイヤー変数に設定します。GA4 を直接呼ぶ場合は、上の dataLayer の 2 行を次に置き換えてください(リスナーは追加しません)。
if (typeof analytics.gtag === 'function') {
analytics.gtag('event', 'generate_lead', { form_slot: slotId })
}CSP では処理後の共有 JS の配信元を script-src で許可します。is:inline などでインラインにする場合は hash または CSP と一致する nonce が必要です。GTM・GA 自体の配信元の script-src と送信先の connect-src なども別途許可してください。
開発中の表示
アプリの設定の「開発用トークン」でトークンを発行してください。発行・利用できるのは admin と developer です。Astro を実行するサーバー側の環境に、次を設定します。値は、開発用トークンの画面に出る環境変数の例に置き換えてください。STATIC_FORMS_DEV_ENVIRONMENT_ID には、アプリのデフォルトブランチの環境の ID が自動で入っているので、そのまま使ってください。表示されるのは、そのアプリに接続したフォームです(環境・ブランチごとには分かれません)。ローカルの画面に「環境・branch に対応する設置がありません。」と出たら、開発用トークンの画面で今の環境 ID を確かめ、STATIC_FORMS_DEV_ENVIRONMENT_ID を置き換えてください。
export STATIC_FORMS_DEV_ENABLED=1
export STATIC_FORMS_DEV_API_URL=https://dashboard.example.com
export STATIC_FORMS_DEV_TOKEN=sfdev_REPLACE_WITH_ISSUED_TOKEN
export STATIC_FORMS_DEV_APP_ID=app-id
export STATIC_FORMS_DEV_ENVIRONMENT_ID=environment-id
export STATIC_FORMS_DEV_BRANCH=feature/contactAPI URL は HTTPS の origin を指定します。STATIC_FORMS_DEV_BRANCH はフォームの選び分けには使いません。作業中のブランチ名を入れておくと、保存データが別のブランチで取ったものでないかを確かめられます。branch を付けずに取った開発用のデータ(scope の branch が *)を使うときは * を入れてください。git からは推測しません。トークンを NEXT_PUBLIC_*・PUBLIC_* に入れないでください。
astro dev の StaticForm.astro は、公開版の入力を開発用プレビューとして表示します。送信はできず、送信用の controller script も出しません。取得に失敗した場合は フォームの位置に診断を表示します。
astro build や実行種別が不明の場合は開発 API を呼びません。ビルド用の入力・pin・STATIC_FORMS_BUILD_ONLY などがあれば、開発サーバーでも本番の入力を使います。入力が無ければエラーになります。
astro build を NODE_ENV=development で実行しないでください。開発表示には astro dev の command と import.meta.env.DEV の両方が必要です。build の出力に開発の印を含む HTML が残った場合も、ビルドを失敗にします。
API のデータを保存して使う場合は、プロジェクトのルートで次を実行します。
npx static-forms pull
export STATIC_FORMS_DEV_SNAPSHOT_PATH=.static-forms/development.json既定の保存先は配信対象外の .static-forms/development.json です。更新には npx static-forms pull --force を使います。保存データにトークンは含まれません。
保存した snapshot は API より優先し、読み込みに失敗しても API に切り替えません。保存データを読む場合も、開発の有効化とアプリ・環境・STATIC_FORMS_DEV_BRANCH の設定は必要です。
