Form Grid
Structure a form's layout with margin utilities, the grid, and gaps — from a simple two-column row to a full horizontal form.
For the <Form> element's own default behaviors and the label/help/validation props shared
across every form component, see Working with Forms.
This page covers structuring a form's layout with the grid system once those pieces are in place.
Utilities
Margin utilities add basic structure to a form — grouping labels, controls, optional help text,
and validation messaging. Stick to margin-bottom utilities in a single direction throughout a
form for consistent spacing.
Form grid
Build more complex forms with <Grid> for layouts that need
multiple columns, varied widths, or additional alignment options. columns gives a row of equal
fields.
import { Grid, TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<Grid columns={2}>
<div>
<TextInput placeholder="First name" aria-label="First name" />
</div>
<div>
<TextInput placeholder="Last name" aria-label="Last name" />
</div>
</Grid>
)
}Gaps
Set gap to a token of the spacing scale to change the space between fields. Without it, a grid
has the gutter of the breakpoint. The .column-gap-{token} and .row-gap-{token} utilities set
the inline and block directions independently.
import { Grid, TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<Grid columns={2} gap="xs">
<div>
<TextInput placeholder="First name" aria-label="First name" />
</div>
<div>
<TextInput placeholder="Last name" aria-label="Last name" />
</div>
</Grid>
)
}The grid also composes into more complex layouts, like a full address form. A <Grid> renders
the <Form> itself with component={Form}, and each field is a <GridItem> that fills the row
with span="full" and narrows from a breakpoint up.
import {
Button,
Checkbox,
Form,
FormLabel,
Grid,
GridItem,
Select,
TextInput
} from '@chassis-ui/react'
export const Example = () => {
return (
<Grid component={Form} gap="md">
<GridItem span={{ base: 'full', md: 6 }}>
<FormLabel htmlFor="inputEmail4" id="inputEmail4Label">
Email
</FormLabel>
<TextInput type="email" id="inputEmail4" aria-labelledby="inputEmail4Label" />
</GridItem>
<GridItem span={{ base: 'full', md: 6 }}>
<FormLabel htmlFor="inputPassword4" id="inputPassword4Label">
Password
</FormLabel>
<TextInput
autoComplete="current-password"
type="password"
id="inputPassword4"
aria-labelledby="inputPassword4Label"
/>
</GridItem>
<GridItem span="full">
<FormLabel htmlFor="inputAddress" id="inputAddressLabel">
Address
</FormLabel>
<TextInput
id="inputAddress"
aria-labelledby="inputAddressLabel"
placeholder="1234 Main St"
/>
</GridItem>
<GridItem span="full">
<FormLabel htmlFor="inputAddress2" id="inputAddress2Label">
Address 2
</FormLabel>
<TextInput
id="inputAddress2"
aria-labelledby="inputAddress2Label"
placeholder="Apartment, studio, or floor"
/>
</GridItem>
<GridItem span={{ base: 'full', md: 6 }}>
<FormLabel htmlFor="inputCity" id="inputCityLabel">
City
</FormLabel>
<TextInput id="inputCity" aria-labelledby="inputCityLabel" />
</GridItem>
<GridItem span={{ base: 'full', md: 4 }}>
<FormLabel htmlFor="inputState">State</FormLabel>
<Select id="inputState">
<option>Choose...</option>
<option>...</option>
</Select>
</GridItem>
<GridItem span={{ base: 'full', md: 2 }}>
<FormLabel htmlFor="inputZip" id="inputZipLabel">
Zip
</FormLabel>
<TextInput id="inputZip" aria-labelledby="inputZipLabel" />
</GridItem>
<GridItem span="full">
<Checkbox id="gridCheck" label="Check me out" />
</GridItem>
<GridItem span="full">
<Button type="submit">Sign in</Button>
</GridItem>
</Grid>
)
}Horizontal form
Create horizontal forms with a <Grid> per form group: the label takes the .col-span-*
classes and the control sits in a <GridItem>, both with span="full" below the breakpoint so
that they stack. Add .col-form-label to a <FormLabel> as well, so it's vertically centered with
its associated form control. .row-gap-zero keeps a stacked label close to its control, and a
control with no label starts at the control column with start.
import {
Button,
Checkbox,
Form,
FormLabel,
Grid,
GridItem,
Radio,
RadioGroup,
TextInput
} from '@chassis-ui/react'
export const Example = () => {
return (
<Form>
<Grid className="row-gap-zero mb-md">
<FormLabel
htmlFor="inputEmail3"
id="inputEmail3Label"
className="col-span-full sm:col-span-2 col-form-label"
>
Email
</FormLabel>
<GridItem span={{ base: 'full', sm: 10 }}>
<TextInput type="email" id="inputEmail3" aria-labelledby="inputEmail3Label" />
</GridItem>
</Grid>
<Grid className="row-gap-zero mb-md">
<FormLabel
htmlFor="inputPassword3"
id="inputPassword3Label"
className="col-span-full sm:col-span-2 col-form-label"
>
Password
</FormLabel>
<GridItem span={{ base: 'full', sm: 10 }}>
<TextInput
autoComplete="current-password"
type="password"
id="inputPassword3"
aria-labelledby="inputPassword3Label"
/>
</GridItem>
</Grid>
<Grid className="row-gap-zero mb-md">
<GridItem span={{ base: 'full', sm: 10 }} start={{ sm: 3 }}>
<RadioGroup label="Radios" defaultValue="option1">
<Radio value="option1" label="First radio" />
<Radio value="option2" label="Second radio" />
<Radio value="option3" label="Third disabled radio" disabled />
</RadioGroup>
</GridItem>
</Grid>
<Grid className="row-gap-zero mb-md">
<GridItem span={{ base: 'full', sm: 10 }} start={{ sm: 3 }}>
<Checkbox id="gridCheck1" label="Example checkbox" />
</GridItem>
</Grid>
<Button type="submit">Sign in</Button>
</Form>
)
}Label sizing
Add .sm or .lg beside .col-form-label on a <FormLabel> or <legend> to match
the size of a .sm or .lg control.
import { FormLabel, Grid, GridItem, TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<Grid className="row-gap-zero mb-md">
<FormLabel
htmlFor="colFormLabelSm"
id="colFormLabelSmLabel"
className="col-span-full sm:col-span-2 col-form-label sm"
>
Email
</FormLabel>
<GridItem span={{ base: 'full', sm: 10 }}>
<TextInput
type="email"
size="sm"
id="colFormLabelSm"
aria-labelledby="colFormLabelSmLabel"
placeholder="col-form-label sm"
/>
</GridItem>
</Grid>
<Grid className="row-gap-zero mb-md">
<FormLabel
htmlFor="colFormLabel"
id="colFormLabelLabel"
className="col-span-full sm:col-span-2 col-form-label"
>
Email
</FormLabel>
<GridItem span={{ base: 'full', sm: 10 }}>
<TextInput
type="email"
id="colFormLabel"
aria-labelledby="colFormLabelLabel"
placeholder="col-form-label"
/>
</GridItem>
</Grid>
<Grid className="row-gap-zero">
<FormLabel
htmlFor="colFormLabelLg"
id="colFormLabelLgLabel"
className="col-span-full sm:col-span-2 col-form-label lg"
>
Email
</FormLabel>
<GridItem span={{ base: 'full', sm: 10 }}>
<TextInput
type="email"
size="lg"
id="colFormLabelLg"
aria-labelledby="colFormLabelLgLabel"
placeholder="col-form-label lg"
/>
</GridItem>
</Grid>
</>
)
}Column sizing
Give each field a span of the twelve columns to size it against the others — like
<GridItem span={{ base: 'full', sm: 6 }}> for a field that takes half the row from the sm
breakpoint.
import { Grid, GridItem, TextInput } from '@chassis-ui/react'
export const Example = () => {
return (
<Grid gap="md">
<GridItem span={{ base: 'full', sm: 6 }}>
<TextInput placeholder="City" aria-label="City" />
</GridItem>
<GridItem span={{ base: 'full', sm: 3 }}>
<TextInput placeholder="State" aria-label="State" />
</GridItem>
<GridItem span={{ base: 'full', sm: 3 }}>
<TextInput placeholder="Zip" aria-label="Zip" />
</GridItem>
</Grid>
)
}Auto-sizing
A grid sizes its columns from the container, so fields as wide as their content go in a
<Flex> instead: wrap lets them move to the next line, align
centers them vertically, and each takes only the space its content needs.
import {
Button,
Checkbox,
Flex,
Form,
FormLabel,
InputGroup,
InputGroupAddon,
Select,
TextInput
} from '@chassis-ui/react'
export const Example = () => {
return (
<Flex component={Form} wrap="wrap" align="center" columnGap="md" rowGap="xs">
<div>
<FormLabel className="visually-hidden" htmlFor="autoSizingInput" id="autoSizingInputLabel">
Name
</FormLabel>
<TextInput
id="autoSizingInput"
aria-labelledby="autoSizingInputLabel"
placeholder="Jane Doe"
/>
</div>
<div>
<FormLabel
className="visually-hidden"
htmlFor="autoSizingInputGroup"
id="autoSizingInputGroupLabel"
>
Username
</FormLabel>
<InputGroup>
<InputGroupAddon>@</InputGroupAddon>
<TextInput
id="autoSizingInputGroup"
aria-labelledby="autoSizingInputGroupLabel"
placeholder="Username"
/>
</InputGroup>
</div>
<div>
<FormLabel className="visually-hidden" htmlFor="autoSizingSelect">
Preference
</FormLabel>
<Select id="autoSizingSelect">
<option>Choose...</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</Select>
</div>
<div>
<Checkbox id="autoSizingCheck" label="Remember me" />
</div>
<div>
<Button type="submit">Submit</Button>
</div>
</Flex>
)
}Combine content-width fields with sized ones through the fraction width utilities: .w-100 sm:w-3/12 is a full-width field that takes a quarter of the row from sm. The fields that don't fit beside them wrap to the next line.
import {
Button,
Checkbox,
Flex,
Form,
FormLabel,
InputGroup,
InputGroupAddon,
Select,
TextInput
} from '@chassis-ui/react'
export const Example = () => {
return (
<Flex component={Form} wrap="wrap" align="center" columnGap="md" rowGap="xs">
<div className="w-100 sm:w-3/12">
<FormLabel
className="visually-hidden"
htmlFor="specificSizeInputName"
id="specificSizeInputNameLabel"
>
Name
</FormLabel>
<TextInput
id="specificSizeInputName"
aria-labelledby="specificSizeInputNameLabel"
placeholder="Jane Doe"
/>
</div>
<div className="w-100 sm:w-3/12">
<FormLabel
className="visually-hidden"
htmlFor="specificSizeInputGroupUsername"
id="specificSizeInputGroupUsernameLabel"
>
Username
</FormLabel>
<InputGroup>
<InputGroupAddon>@</InputGroupAddon>
<TextInput
id="specificSizeInputGroupUsername"
aria-labelledby="specificSizeInputGroupUsernameLabel"
placeholder="Username"
/>
</InputGroup>
</div>
<div className="w-100 sm:w-3/12">
<FormLabel className="visually-hidden" htmlFor="specificSizeSelect">
Preference
</FormLabel>
<Select id="specificSizeSelect">
<option>Choose...</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</Select>
</div>
<div>
<Checkbox id="autoSizingCheck2" label="Remember me" />
</div>
<div>
<Button type="submit">Submit</Button>
</div>
</Flex>
)
}Inline forms
Use a <Flex> that is a column below a breakpoint and a row from it. gap adds the spacing in
both directions, and align: 'center' vertically centers the form elements so the <Checkbox>
aligns properly.
import {
Button,
Checkbox,
Flex,
Form,
FormLabel,
InputGroup,
InputGroupAddon,
Select,
TextInput
} from '@chassis-ui/react'
export const Example = () => {
return (
<Flex
component={Form}
direction={{ base: 'column', lg: 'row' }}
gap="md"
align={{ lg: 'center' }}
>
<div>
<FormLabel
className="visually-hidden"
htmlFor="inlineFormInputGroupUsername"
id="inlineFormInputGroupUsernameLabel"
>
Username
</FormLabel>
<InputGroup>
<InputGroupAddon>@</InputGroupAddon>
<TextInput
id="inlineFormInputGroupUsername"
aria-labelledby="inlineFormInputGroupUsernameLabel"
placeholder="Username"
/>
</InputGroup>
</div>
<div>
<FormLabel className="visually-hidden" htmlFor="inlineFormSelectPref">
Preference
</FormLabel>
<Select id="inlineFormSelectPref">
<option>Choose...</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</Select>
</div>
<div>
<Checkbox id="inlineFormCheck" label="Remember me" />
</div>
<div>
<Button type="submit">Submit</Button>
</div>
</Flex>
)
}