Skip to main content

Form control

Use the form control component to display form inputs alongside labels, validation and more.

import {FormControl} from '@primer/react-brand'

Examples

Basic

FormControl is responsible for layout and ensuring that relevant IDs and ARIA attributes are passed to its children.

<FormControl>
  <FormControl.Label>Name</FormControl.Label>
  <TextInput autoComplete="name" />
</FormControl>

Alternative inputs

<Stack direction="vertical" gap="spacious">
  {/* Select */}
  <FormControl>
    <FormControl.Label>Select</FormControl.Label>
    <Select defaultValue="">
      <Select.Option value="" disabled>
        Select a handle
      </Select.Option>
      <Select.Option value="mona">Monalisa</Select.Option>
      <Select.Option value="hubot">Hubot</Select.Option>
    </Select>
  </FormControl>
  {/* Checkbox */}
  <FormControl>
    <Checkbox />
    <FormControl.Label>Checkbox</FormControl.Label>
    <FormControl.Hint>With an optional message</FormControl.Hint>
  </FormControl>
  {/* Radio */}
  <FormControl>
    <FormControl.Label>Radio</FormControl.Label>
    <FormControl.Hint>With an optional message</FormControl.Hint>
    <Radio />
  </FormControl>
  {/* Textarea */}
  <FormControl fullWidth>
    <FormControl.Label>Textarea</FormControl.Label>
    <Textarea />
  </FormControl>
</Stack>

Layout

FormControl can help compose form layouts quickly, while ensuring the inputs are accessible for assistive technologies.

An example of a typical layout composed using FormControl:

<form>
  <div
    style={{
      alignItems: 'center',
      display: 'grid',
      gap: 16,
      margin: '0 auto ',
      maxWidth: 600,
      paddingBottom: 3,
    }}
  >
    <Text as="p" variant="muted" size="100">
      All fields marked with an asterisk (*) are required
    </Text>
    <div
      style={{
        display: 'grid',
        gridTemplateColumns: '0.5fr 1fr 1fr',
        gap: 16,
      }}
    >
      <FormControl fullWidth>
        <FormControl.Label>Title</FormControl.Label>
        <Select defaultValue="">
          <Select.Option value="" disabled>
            Title
          </Select.Option>
          <Select.Option value="miss">Miss</Select.Option>
          <Select.Option value="mr">Mr</Select.Option>
          <Select.Option value="mrs">Mrs</Select.Option>
          <Select.Option value="mx">Mx</Select.Option>
        </Select>
      </FormControl>
      <FormControl fullWidth required>
        <FormControl.Label>First name</FormControl.Label>
        <TextInput required autoComplete="given-name" />
      </FormControl>
      <FormControl fullWidth required>
        <FormControl.Label>Last name</FormControl.Label>
        <TextInput required autoComplete="family-name" />
      </FormControl>
    </div>
 
    <FormControl fullWidth required>
      <FormControl.Label>Enterprise name</FormControl.Label>
      <TextInput required />
    </FormControl>
 
    <FormControl fullWidth required>
      <FormControl.Label>Enterprise URL</FormControl.Label>
      <TextInput leadingText="github.com/" required />
    </FormControl>
 
    <FormControl fullWidth required>
      <FormControl.Label>Country</FormControl.Label>
      <Select defaultValue="">
        <Select.Option value="" disabled>
          Country
        </Select.Option>
        <Select.Option value="us">United States of America</Select.Option>
        <Select.Option value="uk">United Kingdom</Select.Option>
      </Select>
    </FormControl>
    <FormControl hasBorder required>
      <Checkbox />
      <FormControl.Label>Contact me about GitHub Enterprise Server</FormControl.Label>
      <FormControl.Hint>
        <Text size="200" variant="muted">
          I&apos;m interested in learning more about{' '}
          <InlineLink size="200" href="https://github.com/enterprise" target="_blank">
            GitHub Enterprise Server
          </InlineLink>{' '}
          and would like to be contacted by GitHub’s sales team.
        </Text>
      </FormControl.Hint>
    </FormControl>
    <div
      style={{
        borderWidth: 1,
        borderStyle: 'solid',
        borderColor: 'var(--brand-control-color-border-default)',
        backgroundColor: 'var(--brand-color-canvas-inset)',
        height: 150,
        width: '100%',
        borderRadius: 6,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      <Text size="200" variant="muted">
        Captcha
      </Text>
    </div>
    <FormControl required>
      <Checkbox />
      <FormControl.Label>
        <Text size="200" variant="muted">
          I hereby accept the{' '}
          <InlineLink size="200" href="https://github.com/customer-terms" target="_blank">
            GitHub Customer Agreement
          </InlineLink>{' '}
          on behalf of my organization and confirm that I have the authority to do so. For more information about
          GitHub&apos;s privacy practices, see the{' '}
          <InlineLink
            size="200"
            href="https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement"
            target="_blank"
          >
            GitHub Privacy Statement.
          </InlineLink>{' '}
        </Text>
      </FormControl.Label>
    </FormControl>
    <div
      style={{
        justifyContent: 'end',
        display: 'inline-grid',
        gap: 16,
      }}
    >
      <Button variant="primary" type="submit">
        Start trial
      </Button>
    </div>
  </div>
</form>

Visually hidden label

Labels should only be visually hidden when the context is clear from the input itself. For example, a search input with a search icon. The majority of the time, labels should be visible.

<FormControl>
  <FormControl.Label visuallyHidden>Search</FormControl.Label>
  <TextInput type="search" trailingVisual={<SearchIcon />} />
</FormControl>

Validation

The following example demonstrates declarative form validation in controlled mode.

When the form is submitted with an invalid value, the invalid input receives focus to help the user correct the error. This is especially important for users navigating the form using a screen reader.

More information on form validation best practices can be found in the Primer UI Patterns documentation.

Try changing the input value to monalisa and submitting the form to show the success state.

const App = () => { const inputRef = React.useRef(null) const [value, setValue] = React.useState('mona lisa') const [isValid, setIsValid] = React.useState(false) const onChange = e => setValue(e.target.value) const onSubmit = e => { e.preventDefault() const valid = !value.includes(' ') setIsValid(valid) if (!valid) { inputRef.current.focus() } } return ( <form onSubmit={onSubmit}> <Stack gap="normal"> <FormControl validationStatus={isValid ? 'success' : 'error'} fullWidth> <FormControl.Label>GitHub handle</FormControl.Label> <TextInput ref={inputRef} fullWidth value={value} onChange={onChange} /> {isValid && <FormControl.Validation>Valid name</FormControl.Validation>} {!isValid && ( <FormControl.Validation> GitHub handles cannot contain spaces. {value && `Did you mean "${value.replaceAll(' ', '')}"`} </FormControl.Validation> )} </FormControl> <Button type="submit">Submit</Button> </Stack> </form> ) } render(App)

Hint

FormControl.Hint can be used to provide additional context or guidance to the user. FormControl.Hint must be a direct child of FormControl.

<FormControl>
  <FormControl.Label>Select</FormControl.Label>
  <Select>
    <Select.Option value="" disabled>
      Select a handle
    </Select.Option>
    <Select.Option value="mona">Monalisa</Select.Option>
    <Select.Option value="hubot">Hubot</Select.Option>
  </Select>
  <FormControl.Hint>With an optional message</FormControl.Hint>
</FormControl>

Full width

Pass the fullWidth prop to FormControl to provide additional behavior and state context to all children, rather than the input only.

<FormControl fullWidth>
  <FormControl.Label>Name</FormControl.Label>
  <TextInput autoComplete="name" />
</FormControl>

Sizes

FormControl can appear in medium and large dimensions using the size prop.

<div style={{display: 'grid', gap: 3}}>
  <FormControl size="medium">
    <FormControl.Label>Medium</FormControl.Label>
    <TextInput />
  </FormControl>
 
  <FormControl size="large">
    <FormControl.Label>Large</FormControl.Label>
    <TextInput />
  </FormControl>
</div>

Required

Pass the required prop to FormControl to provide additional behavior and state context to all children, rather than the input only.

When marking a field as required, it is recommended to also provide a corresponding message at the start of the form informing the user that “all fields marked with an asterisk (*) are required”.

<FormControl required>
  <FormControl.Label>Name</FormControl.Label>
  <TextInput autoComplete="name" />
</FormControl>

Using refs

FormControl inputs can be used in uncontrolled mode by forwarding a ref to the underlying element.

const App = () => { const inputRef = React.useRef(null) const handleSubmit = e => { e.preventDefault() if (inputRef.current) { alert(`Name: ${inputRef.current.value}`) } } return ( <form onSubmit={handleSubmit}> <div style={{ display: 'grid', gap: 'var(--base-size-16)', maxWidth: 400, marginX: 'auto', }} > <FormControl fullWidth> <FormControl.Label>Name</FormControl.Label> <TextInput ref={inputRef} defaultValue="Mona Lisa" autoComplete="name" /> </FormControl> <Button type="submit" variant="primary"> Submit </Button> </div> </form> ) } render(App)

Component props

FormControl Required

FormControl passes contextual data to its child inputs, labels, validation messaging and more.

NameTypeDefaultDescription
children'FormControl.Label'
'Checkbox'
'Select'
'TextInput'
'FormControl.Validation'
'React.ReactElement'
,
Valid child nodes
classNamestringSets a custom class
idstringSets a custom id
fullWidthbooleanStretches elements visually to the edges of its parent container.
refReact.RefObjectForward a Ref to the underlying DOM node
size'medium'
'large'
'medium'
'large'
Visual dimensions for input and label
validationStatus'error'
'success'
'error'
'success'
Applies visual and semantic state to the underlying elements

FormControl.Label Required

FormControl.Label should be provided for the FormControl to be accessible to assistive technology, but it may be visually hidden.

NameTypeDefaultDescription
childrenstringLabel text
classNamestringApplies a custom class
idstringSets a custom id
refReact.RefObjectForward a Ref to the underlying DOM node
size'medium'
'large'
'medium'
'large'
'medium'Set visual dimensions
visuallyHiddenbooleanfalseHide for sighted users

Additional props can be passed to the <label> element. See MDN for a list of props accepted by the <label> element.

FormControl.Validation

NameTypeDefaultDescription
childrenstringValidation message
classNamestringFormControl.Validation custom class
idstringSets a custom id
refReact.RefObjectForward a Ref to the underlying DOM node
validationStatus'error'
'success'
'error'
'success'
Applies a visual and semantic validation state to the underlying elements