ComponentsForm Panel
Form Panel
sectionThe full form block — sectioned fields, inline validation and a sunken action bar.
Form panel
Sectioned fields, validation & an action bar — the form block.
Best practices
Do
- One column by default — label above control; every field is a .field carrying its label, control and .field__hint.
- Show validation inline: aria-invalid on the control + the message in .field__error, next to the field it belongs to.
- Commit actions live in the sunken foot — one .btn--primary, secondaries as .btn--ghost.
Don't
- Don't use placeholders as labels — they vanish the moment the user types.
- Don't disable the submit button while the user is still typing; validate on blur and on submit.
New workspace
Set up a workspace for your team.
Seats
Phone (billing alerts)
Plan
Email notifications
Send a weekly digest to the owner.
You can change these later in Settings.
Composes: Input · Form primitives · Button · Select · Number Input · Phone Input · Switch · Checkbox & Radio
Best practices
Do
- One column by default — label above control; every field is a .field carrying its label, control and .field__hint.
- Show validation inline: aria-invalid on the control + the message in .field__error, next to the field it belongs to.
- Commit actions live in the sunken foot — one .btn--primary, secondaries as .btn--ghost.
Don't
- Don't use placeholders as labels — they vanish the moment the user types.
- Don't disable the submit button while the user is still typing; validate on blur and on submit.
Recipe CSS
This is the exact CSS your kit ships for Form Panel — token-driven, so it re-themes with every knob. Get it (and the rest) from the configurator's Use kit panel.
/* === Form panel ===
The editing-surface BLOCK — what you reach for when a screen needs a real form,
not a loose stack of inputs. A bordered panel with a titled header, a body of
labelled fields on a responsive grid, sectioned groups, an inline validation
summary, and a footer action bar (Cancel / Save). COMPOSES the field atoms
(.field / .lab + .in · .select · .numinput · .phoneinput · .switch · .radio-card)
and .buttons, so a labelled, validated, sectioned form is rebuildable from the
kit — not hand-assembled per screen.
Anatomy
.formpanel frame (border + radius + surface)
.formpanel__head title + description (divider below)
.formpanel__body padded field area (vertical rhythm)
.formpanel__error inline validation summary (when submit fails)
.formpanel__grid responsive 2-up field grid; a child takes
.formpanel__full to span the full width
.formpanel__section a titled sub-group (divider above)
.formpanel__section-title
.formpanel__foot action bar — note (left) + buttons (right) */
.formpanel { border: 1px solid var(--k-border); border-radius: var(--k-radius-md); background: var(--k-surface); overflow: hidden; container: formpanel / inline-size; }
/* Container query (B2) — the field grid already collapses to one column via
* auto-fit; the query adds what auto-fit can't: under ~380px the action FOOT
* stacks (note above, buttons full-width block) so the primary action stays
* thumb-reachable on a narrow container. Responds to the panel's own width. */
@container formpanel (max-width: 380px) {
.formpanel__foot { flex-direction: column; align-items: stretch; }
.formpanel__foot-note { margin-right: 0; }
.formpanel__foot .btn { width: 100%; }
}
.formpanel__head { padding: var(--k-s-16); border-bottom: var(--k-divider); }
.formpanel__title { font-size: var(--k-type-h3); font-weight: var(--k-weight-semibold); color: var(--k-fg); }
.formpanel__desc { font-size: var(--k-type-small); color: var(--k-fg-muted); margin-top: var(--k-s-2); }
.formpanel__body { padding: var(--k-s-16); display: flex; flex-direction: column; gap: var(--k-s-16); }
/* Responsive field grid — two columns on a roomy panel, collapses to one when the
* panel is narrow (no container query needed: minmax floor + auto-fit). This is
* the field-layout grammar a bare card lacks; a full-width field opts out. */
.formpanel__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); gap: var(--k-s-16); }
.formpanel__full { grid-column: 1 / -1; }
/* A titled sub-group inside the body — divider above + an eyebrow label, so a long
* form reads as sections ("Profile", "Security") instead of one undifferentiated wall. */
.formpanel__section { display: flex; flex-direction: column; gap: var(--k-s-16); padding-top: var(--k-s-16); border-top: var(--k-divider); }
.formpanel__section-title { font-size: var(--k-type-eyebrow); font-weight: var(--k-weight-semibold); text-transform: uppercase; letter-spacing: var(--k-track-eyebrow); color: var(--k-fg-muted); }
/* Inline validation summary — an alert band at the top of the body when submit
* fails, in the danger-soft tone so it matches the .alert atom's error variant. */
.formpanel__error { display: flex; align-items: flex-start; gap: var(--k-s-8); padding: var(--k-s-10) var(--k-s-12); border-radius: var(--k-radius-sm); background: var(--k-danger-soft); color: var(--k-danger-soft-fg, var(--k-danger)); font-size: var(--k-type-small); }
.formpanel__error svg { flex: none; }
/* Footer action bar — mirrors the data-table foot surface for family consistency:
* a sunken band with a top divider, an optional note on the left, the primary
* action trailing right. */
.formpanel__foot { display: flex; align-items: center; gap: var(--k-s-12); flex-wrap: wrap; }
.formpanel__foot-note { margin-right: auto; font-size: var(--k-type-small); color: var(--k-fg-muted); }
/* --horizontal: labels-on-left (Tailwind "form layout with labels on left"). Each
.lab row reflows to [label | control] — the label in a fixed leading column, a
hairline dividing the rows — the dense settings-form layout. Collapses back to
stacked on a narrow container (the label column would crush the control).
Override the label width with --k-form-label-w. */
.formpanel--horizontal .formpanel__body { gap: 0; }
.formpanel--horizontal .formpanel__grid { display: block; }
.formpanel--horizontal .lab {
display: grid;
grid-template-columns: var(--k-form-label-w, 11rem) minmax(0, 1fr);
align-items: center;
gap: var(--k-s-12);
padding: var(--k-s-14) 0;
border-top: var(--k-divider);
}
.formpanel--horizontal .lab > span:first-child { align-self: center; }
.formpanel--horizontal .formpanel__grid > .lab:first-of-type { border-top: 0; }
@container formpanel (max-width: 34rem) {
.formpanel--horizontal .lab { grid-template-columns: 1fr; gap: var(--k-s-4); padding: var(--k-s-10) 0; }
}