Skip to content

Form layout

In review
Live · web
Required
Profile

Shown on your sessions and in the team directory.

Your display name across Nockerl.

Optional. Shown in the team directory.

PreferencesOptional

Defaults for new sessions and how we reach you.

Optional. The default for new sessions.

Weekly digestA Monday summary of every session's activity.
MentionsNotify me when an agent needs a decision.
Product updatesOccasional notes on new Nockerl features.
Preferences sync to every session on this account within a minute.
// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform; this is the intended API,
// not yet exported from @dizyx/nockerl-react (promotion tracked).) Form layout is the COMPOSITION that
// arranges fields: the section (a lifted card + header), the row grid (single/two
// column), the settings row (label+desc left, control right), and the actions footer.
// The fields themselves are NockerlTextField / ValidatedField; this is the scaffold around them.
import { NockerlButton, NockerlTextField, NockerlSwitch } from '@dizyx/nockerl-react';
export function AccountForm() {
return (
<Form onSubmit={save} aria-label="Account settings" maxWidth="md">
<FormSection title="Profile" description="Shown on your sessions and in the directory.">
<FormRow columns={2}>
<NockerlTextField label="Full name" required value={name} onValueChange={setName} />
<NockerlTextField label="Handle" required leadingIcon="@" value={handle} onValueChange={setHandle} />
<NockerlTextField label="Work email" type="email" required span value={email} onValueChange={setEmail} />
<Select label="Role" optional value={role} onValueChange={setRole} options={roles} />
</FormRow>
</FormSection>
<FormSection title="Preferences" optional description="Defaults for new sessions and how we reach you.">
<SettingRow label="Weekly digest" description="A Monday summary of every session.">
<NockerlSwitch checked={digest} onChange={setDigest} ariaLabel="Weekly digest" />
</SettingRow>
<SettingRow label="Mentions" description="Notify me when an agent needs a decision.">
<NockerlSwitch checked={mentions} onChange={setMentions} ariaLabel="Mentions" />
</SettingRow>
</FormSection>
<FormActions sticky={false}>
<NockerlButton text="Cancel" variant="ghost" onClick={cancel} />
<NockerlButton text="Save changes" variant="primary" type="submit" loading={saving} />
</FormActions>
</Form>
);
}
PropTypeDefaultDescription
Form · onSubmit(e: FormEvent) => voidSubmit handler on the underlying <form>. Wire the primary action’s type="submit" to it so Enter submits.
Form · maxWidth'sm' | 'md' | 'lg' | number'md'Bounded content width so sections, the two-column grid, and the footer all align to one edge.
FormSection · title *stringSection header, rendered uppercase + tracked in the muted token (the Voice SettingsCard idiom).
FormSection · descriptionstringOne muted help line under the header.
FormSection · optionalbooleanfalseMarks a whole section optional with an Optional tag beside the title.
FormSection · headerAccessoryReactNodeOptional slot trailing the header, center-aligned: the ⓘ InfoTip seat (settings grammar). Empty → the header renders exactly as before.
FormSection · footerReactNodeOptional card footer INSIDE the section under a full-bleed hairline, for muted hints or quiet section actions (settings grammar).
FormRow · columns1 | 21Single-column (stacked) or two-column grid. Fields stay aligned to the grid; collapses to one column when narrow.
field · spanbooleanfalseIn a two-column row, makes a field span both columns (e.g. email, bio).
field · requiredbooleanfalseAdds a status-colored * and sets the control required. Paired with a form-level legend, never color alone.
field · optionalbooleanfalseAdds an Optional marker for forms that mark optional rather than required.
SettingRow · label / description *stringThe settings-style row: label + description on the LEFT, a control (its child) on the RIGHT. The control is a separate focusable target.
FormActions · stickybooleanfalsePins the right-aligned Cancel/Save footer to the bottom of the scroll area on long forms.
Cross-platform drift (tracked for reconciliation)

The scaffold (a Form of FormSections + FormRow/SettingRow + FormActions) is the target; these are the real divergences between the shipped platforms:

  • Section grouping (resolved): grouping is the lifted section card everywhere (cards lift, per Law 2 applied to forms); the divider + header on a flat surface is retired. Voice’s SettingsCard already matches; Android’s flat HorizontalDivider + header conforms to the card.
  • No extracted form component: neither app ships a Form / FormSection type. Both hand-assemble the Column / VStack, the spacing, and the footer per screen. An extracted scaffold is owed on all three (it follows the text-field drift; there is no NockerlTextField / NockerlForm yet).
  • Required / optional markers (resolved): the required * / Optional markers are part of the shared contract on all platforms. The web * + legend + Optional tag port to Android (today required-in-copy) and Voice.
  • Two-column layout: designed here (and shown live). Android forms are single-column (sheets are narrow); Voice caps width and stacks. Neither ships a responsive two-column grid yet.
  • Sticky footer: web-only. Android keeps Save inline at the end of the scroll; Voice places it inline in/after the card.