Skip to main contentSkip to docs navigation

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.

JSX
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.

JSX
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.

JSX
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.

Radios
JSX
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.

JSX
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.

JSX
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.

@
JSX
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.

@
JSX
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.

@
JSX
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>
  )
}