Form

Molecule
Stitch your own fields inside it; Form adds the stacked or two-column rhythm, titled sections, the submit and cancel actions row, and Enter to submit.

Playground

Every control sets a real prop of FormProps. The JSX below updates as you go.
Email
Password
Props
twoColumn
boolean
submitLabel
string
cancelLabel
string
disabled
boolean
JSX · FormProps
<Form twoColumn submitLabel="Save" cancelLabel="Cancel" />

Variants

9 examples from src/molecules/form/form.md, each rendered live from its own JSX.
First name
Last name
Email
Two-column
<Form twoColumn submitLabel="Create">
<Input label="First name" placeholder="Ada" />
<Input label="Last name" placeholder="King" />
<Input label="Email" placeholder="ada@example.com" />
</Form>
Email
Password
Measure
<Form sm start submitLabel="Sign in">
<Input label="Email" placeholder="sm step, pinned to the start (384)" />
<Input label="Password" />
</Form>
Personal info
This information will be displayed on your public profile.
Full name
Email
Notifications
Choose how you'd like to be notified.
Sections
<Form submitLabel="Save">
<FormSection title="Personal info" description="This information will be displayed on your public profile.">
<Input label="Full name" defaultValue="Rachel Chen" />
<Input label="Email" defaultValue="rachel@example.com" />
</FormSection>
<FormSection title="Notifications" description="Choose how you'd like to be notified.">
<Checkbox defaultChecked>Email notifications</Checkbox>
<Checkbox>SMS alerts</Checkbox>
</FormSection>
</Form>
Email address
Password
Username
Phone number
+1
Date of birth
Account details
<Form submitLabel="Create account">
<Input label="Email address" leadingIcon icon="mail" placeholder="example@domain.com" keyboardType="email-address" autoCapitalize="none" autoComplete="email" />
<Input label="Password" leadingIcon icon="lock" secureTextEntry passwordToggle placeholder="••••••••" autoComplete="new-password" />
<Input label="Username" leadingIcon icon="user" placeholder="yourname" autoCapitalize="none" autoComplete="username" />
<PhoneInput label="Phone number" placeholder="Add your phone number" />
<Input label="Date of birth" leadingIcon icon="calendar" placeholder="MM/DD/YYYY" keyboardType="numbers-and-punctuation" autoComplete="birthdate-full" />
</Form>
Card Number
Expiry Date
CVV
Cardholder name
Credit card
<Form submitLabel="Pay">
<Input label="Card Number" trailingIcon icon="creditCard" placeholder="•••• •••• •••• ••••" keyboardType="number-pad" autoComplete="cc-number" textContentType="creditCardNumber" />
<Row relaxed>
<Input label="Expiry Date" placeholder="MM/YY" keyboardType="number-pad" autoComplete="cc-exp" textContentType="creditCardExpiration" />
<Input label="CVV" placeholder="•••" secureTextEntry keyboardType="number-pad" maxLength={4} autoComplete="cc-csc" textContentType="creditCardSecurityCode" />
</Row>
<Input label="Cardholder name" placeholder="Name on card" autoCapitalize="words" autoComplete="cc-name" textContentType="creditCardName" />
</Form>
Card Number
Expiry Date
CVV
Cardholder name
Credit card with errors
<Form submitLabel="Pay">
<Field label="Card Number" error="Invalid card number.">
<Input trailingIcon icon="creditCard" defaultValue="•••• •••• 1478" keyboardType="number-pad" />
</Field>
<Row relaxed alignStart>
<Field label="Expiry Date" error="Invalid expiration date.">
<Input defaultValue="13/26" keyboardType="number-pad" />
</Field>
<Field label="CVV" error="CVV must be 3 digits.">
<Input defaultValue="66" keyboardType="number-pad" maxLength={4} />
</Field>
</Row>
<Field label="Cardholder name" error="Name can only contain letters.">
<Input defaultValue="Daniel Smith 87" autoCapitalize="words" />
</Field>
</Form>
Street address
Apartment, suite, unit (optional)
City
ZIP code
Country
Address
<Form submitLabel="Save address">
<Input label="Street address" placeholder="123 Main Street" autoComplete="street-address" textContentType="streetAddressLine1" />
<Input label="Apartment, suite, unit (optional)" placeholder="Apt 5B" textContentType="streetAddressLine2" />
<Row relaxed>
<Input label="City" placeholder="San Francisco" textContentType="addressCity" />
<Input label="ZIP code" placeholder="94105" keyboardType="number-pad" autoComplete="postal-code" textContentType="postalCode" />
</Row>
<Select
label="Country"
defaultValue="US"
options={[
{ value: "US", label: "United States", leading: "🇺🇸" },
{ value: "CA", label: "Canada", leading: "🇨🇦" },
{ value: "GB", label: "United Kingdom", leading: "🇬🇧" },
{ value: "DE", label: "Germany", leading: "🇩🇪" },
{ value: "JP", label: "Japan", leading: "🇯🇵" },
]}
/>
</Form>
Street address
Apartment, suite, unit (optional)
City
ZIP code
Country
Address with errors
<Form submitLabel="Save address">
<Field label="Street address" error="Street address is required.">
<Input placeholder="123 Main Street" />
</Field>
<Field label="Apartment, suite, unit (optional)" error="Street address is required.">
<Input placeholder="Apt 5B" />
</Field>
<Row relaxed alignStart>
<Field label="City" error="City is required.">
<Input placeholder="San Francisco" />
</Field>
<Field label="ZIP code" error="Invalid postal code.">
<Input defaultValue="101" keyboardType="number-pad" />
</Field>
</Row>
<Select
label="Country"
defaultValue="US"
options={[
{ value: "US", label: "United States", leading: "🇺🇸" },
{ value: "CA", label: "Canada", leading: "🇨🇦" },
{ value: "GB", label: "United Kingdom", leading: "🇬🇧" },
]}
/>
</Form>
Role
Notifications
Select and switch controls
<Form submitLabel="Save">
<Select label="Role" options={["Admin", "Editor", "Viewer"]} defaultValue="Editor" />
<Switch defaultChecked>Notifications</Switch>
</Form>

Anatomy

Email
Password
1
Section
2
Field
3
Actions
Dimmed parts appear only in some states or variants.

Props

12 props on FormProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
12 props
children
ReactNode
–
The form rows, stitched by the caller: Input, Select, Checkbox, a FormSection, any node. Each control keeps its own state (controlled or uncontrolled); Form never intercepts it.
twoColumn
boolean
false
Two-column: rows flow into a grid of exactly two equal columns, 16 apart, where a FormSection takes the whole line; in a form 512 px wide or narrower (the lg width step, measured on the form itself) the rows stack in one full-width column instead. Omit for the stacked default.
hints
ReactNode
–
Leading content of the actions row, before the buttons (keyboard hints such as "↵ to save"). The row then spreads the hints and the buttons to its two ends.
submitLabel
string
–
Label for the primary submit button. Passing it (or cancelLabel) renders the actions row.
cancelLabel
string
–
Renders a ghost cancel button before the submit button.
block
boolean
false
The actions take block: the cancel and submit buttons split the actions row equally (the Layout page's compact invite form, where the buttons go full width).
onSubmit
() => void
–
Fired when the submit button is pressed, and on the web when Enter is pressed inside one of the form's single-line text fields. Form owns no field state, so the caller reads its own values (no payload).
onCancel
() => void
–
Fired when the cancel button is pressed.
disabled
boolean
false
Disables the actions row and Enter-to-submit, and dims the form.
accessibilityLabel
string
–
The form's name for assistive tech, a brief statement of its purpose ("Sign in", "Invite a teammate"). Form is a form landmark on the web, and ARIA asks every form landmark for a name: it is how a landmark list tells two forms on one page apart. Omit it and the form stays unnamed, as before.
testID
string
–
E2E hook forwarded to the root element.
style
LayoutStyle
–
Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds.
MeasureProps · width steps xxxs 192 · xxs 256 · xs 320 · sm 384 · md 448 · lg 512 · xl 576 · xxl 672 · xxxl 768 · wide 896 · wider 1024 · widest 1152 · page 1280, plus start to pin to the leading edge.

Accessibility and keyboard

Keyboard
Type
Edits the value
Tab
Next field
Escape
Clears when clearable
Semantics
Roles
group
ARIA on web
aria-label

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
Card Number
Expiry Date
CVV
Cardholder name
Web
control 10 · field 10 · card 16 · Instrument Sans
Card Number
Expiry Date
CVV
Cardholder name
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
Card Number
Expiry Date
CVV
Cardholder name
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

On the web, Enter confirms an active Autocomplete suggestion before it can submit the form. A subsequent Enter submits after the list closes. Enter used to confirm an input-method candidate never submits, and holding Enter does not repeat submission. Multiline fields retain Enter for newlines. On native platforms, each field's onSubmitEditing owns the return-key action.
Form is a composition surface: you stitch the field atoms as children and keep their state; Form supplies the vertical rhythm, the actions row, and onSubmit, which fires from the submit button or from Enter in a single-line field on the web.
On the web a Form is a form landmark. Name it with accessibilityLabel, a brief statement of its purpose such as "Sign in", so a screen reader's landmark list can tell it apart; a page that holds more than one form needs a distinct name on each.
<Form submitLabel="Sign in">
<Input label="Email" placeholder="you@example.com" />
<Input label="Password" />
</Form>

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--bg2
--bls
--bw
--fd
--ft
--ion
--ion-fg
--ln
--ln2
--tx
--tx2
--tx3

Related components