コンテンツにスキップ

設定ページ

hc-shell のアプリフレーム — ヘッダー、サイドバーナビゲーション、スクロールする main — の main リージョンが、実際に配線された アカウント設定ブロックになっています: hc-card のフォームに、 常設ヒントつきの hc-field の行を並べ、 mutating-form 流にサブミットします。422 はサーバの フィールドエラーフラグメントを 名指しされたフィールドへ分配し、成功は トーストを上げます。

3 つの分岐をすべて試せます: 空のメールでサブミット(required エラーが Email フィールドの下に着地)、taken@example.com で サブミット(duplicate エラー)、それ以外のメールでサブミット (**「Member saved」**トースト — 空の 200 ボディがエラーサマリーを 消します)。

デモのフォームは api/recipes/field-errors/members に POST します。 その契約 — 失敗は 422 + フラグメント、成功は 200 + hc:toast — の おかげでラウンドトリップ全体がこのページ上で完結します。 mutating-form 契約 自身の成功分岐はリダイレクト(HX-Redirect、ウィンドウ全体の ナビゲーション)で、そちらはレシピページがデモしています。保存後も その場に留まる設定ページには、まさにここで見せているトースト変種を 使います。

Acme Admin
Account settings

Used for sign-in and notifications — we never share it.

Shown to the rest of your team.

実アプリ形の URL(/settings/profile — 自分のエンドポイントに差し 替えてください)で書いた完全なページです。トーストのマークアップは 不要です: installToast() が自分でリージョンを作ります。

<body>
<div class="hc-shell">
<header class="hc-shell__header">
<button class="hc-button hc-shell__toggle" data-variant="ghost"
data-hc-shell-toggle aria-label="Open navigation" type="button"></button>
<strong>Acme Admin</strong>
</header>
<nav class="hc-shell__sidebar" aria-label="Primary">
<a href="/dashboard">Dashboard</a>
<a href="/members">Members</a>
<a href="/settings" aria-current="page">Settings</a>
</nav>
<main class="hc-shell__main">
<!-- method/action が非 JS のサブミットを生かします(post/redirect/get)。 -->
<form class="hc-card" style="max-inline-size: 34rem;"
method="post" action="/settings/profile"
data-hx-post="/settings/profile"
data-hx-target="#settings-errors"
data-hx-swap="innerHTML"
data-hx-disabled-elt="find button[type=submit]"
data-hx-indicator="find .hc-spinner">
<div class="hc-card__header"><strong>Account settings</strong></div>
<div class="hc-card__body hc-stack" style="--hc-stack-gap: 1rem;">
<!-- 422 のフィールドエラーフラグメントはここにスワップされ、
installFieldErrors が各項目を名指しされたフィールドへ分配します。 -->
<div id="settings-errors"></div>
<div class="hc-field">
<label class="hc-field__label" for="settings-email">Email</label>
<input class="hc-input" id="settings-email" name="email" type="email"
aria-describedby="settings-email-hint" />
<!-- __hint はフィールドが invalid でもミュートのままです。 -->
<p class="hc-field__hint" id="settings-email-hint">
Used for sign-in and notifications — we never share it.
</p>
<p class="hc-field__error" aria-live="polite"></p>
</div>
<div class="hc-field">
<label class="hc-field__label" for="settings-display-name">Display name</label>
<input class="hc-input" id="settings-display-name" name="display_name" type="text"
aria-describedby="settings-display-name-hint" />
<p class="hc-field__hint" id="settings-display-name-hint">
Shown to the rest of your team.
</p>
</div>
<label class="hc-switch-label">
<input class="hc-switch" name="notify" type="checkbox" role="switch" checked />
Email notifications
</label>
</div>
<div class="hc-card__footer hc-cluster"
style="--hc-cluster-gap: 0.5rem; justify-content: flex-end;">
<span class="hc-spinner htmx-indicator" aria-hidden="true"></span>
<button class="hc-button" data-variant="primary" type="submit">Save changes</button>
</div>
</form>
</main>
</div>
</body>
リージョンコンポーネント / ブロックレシピ契約
アプリフレームhc-shell—(純 CSS。installShell() はモバイルナビ担当)
設定カードhc-card + アカウント設定ブロックmutating-formmutating-form/contract.md
フィールド + ヒントhc-field + hc-input
エラーサマリー + 分配hc-alertfield-errorsfield-errors/contract.md
成功トーストhc-toasttoasttoast/contract.md
  • エンドポイントを差し替える。 /settings/profile はプレース ホルダーです — サーバは mutating-form / field-errors の契約(422 + フラグメント、成功 + トーストまたは HX-Redirect)を 実装するだけです。
  • 本物の CSRF。 htmx パスには <meta name="csrf-token">installCsrfHeader() を、非 JS フォームにはフレームワークの隠し フィールドを追加します — CSRF トークン を参照。
  • 本物の認証。 デモ API は匿名です。エンドポイントはフレームワーク のセッション認証で守ってください — サーバインテグレーションガイド にスタックごとのイディオムがあります。
  • エラー文言。 メッセージは人間らしく — ライティングガイド を参照。