Settings page
An hc-shell app frame —
header, sidebar navigation, scrolling main — whose main region is the
account-settings block wired up for
real: an hc-card form of
hc-field rows with
persistent hints, submitting the
mutating-form way. A
422 distributes the server’s
field-errors fragment to
the fields it names; success raises a
toast.
Live template
Section titled “Live template”Try all three branches: submit with a blank email (the required
error lands under the Email field), with taken@example.com (the
duplicate error), and with any other email (a “Member saved”
toast — the empty 200 body clears the error summary).
The demo form posts to api/recipes/field-errors/members, whose
contract — 422 + fragment on failure, 200 + hc:toast on success —
keeps the whole round trip on this page. The
mutating-form contract’s
own success branch is a redirect (HX-Redirect, a full window
navigation) — its recipe page demos that; a settings page that stays
put on save uses exactly the toast variant shown here.
Page skeleton
Section titled “Page skeleton”The complete page, with real-app-shaped URLs (/settings/profile —
swap in your own endpoint). No toast markup is needed:
installToast() creates its own region.
<body> <div class="hc-shell"> <header class="hc-shell__header"> <button class="hc-button" 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 keep the no-JS submit working (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;"> <!-- The 422 field-errors fragment swaps in here; installFieldErrors distributes each item to the field it names. --> <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 stays muted even when the field is 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>Wiring map
Section titled “Wiring map”| Region | Component / block | Recipe | Contract |
|---|---|---|---|
| App frame | hc-shell | — | — (pure CSS; installShell() powers mobile nav) |
| Settings card | hc-card + account-settings block | mutating-form | mutating-form/contract.md |
| Fields + hints | hc-field + hc-input | — | — |
| Error summary + distribution | hc-alert | field-errors | field-errors/contract.md |
| Success toast | hc-toast | toast | toast/contract.md |
Adapt it
Section titled “Adapt it”- Rename the endpoints.
/settings/profileis a placeholder — the server just has to implement the mutating-form / field-errors contracts (422 + fragment, success + toast orHX-Redirect). - Real CSRF. Add
<meta name="csrf-token">andinstallCsrfHeader()for the htmx path, plus your framework’s hidden field for the no-JS form — see CSRF tokens. - Real auth. The demo API is anonymous; gate your endpoint with your framework’s session auth — the server integration guides show the idioms per stack.
- Error copy. Keep messages human — see the writing guide.