Back to React Spectrum

Form

packages/react-aria-components/docs/Form.mdx

2022-12-1612.7 KB
Original Source

{/* Copyright 2020 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs'; export default Layout;

import docs from 'docs:react-aria-components'; import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; import packageData from 'react-aria-components/package.json'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; import {Keyboard} from '@react-spectrum/text'; import {StarterKits} from '@react-spectrum/docs/src/StarterKits';


category: Forms keywords: [input, form, field, validation] type: component

Form

<PageDescription>{docs.exports.Form.description}</PageDescription>

<HeaderInfo packageData={packageData} componentNames={['Form']} sourceData={[ {type: 'W3C', url: 'https://www.w3.org/TR/wai-aria-1.2/#form'} ]} />

Example

tsx
import {Form, TextField, Label, Input, FieldError, Button} from 'react-aria-components';

<Form>
  <TextField name="email" type="email" isRequired>
    <Label>Email</Label>
    <Input />
    <FieldError />
  </TextField>
  <Button type="submit">Submit</Button>
</Form>
<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
css
@import "@react-aria/example-theme";
@import './TextField.mdx' layer(textfield);
@import './Button.mdx' layer(button);
css
.react-aria-Form {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 8px;
}
</details>

Features

The HTML <form> element can be used to build forms. React Aria's Form component extends HTML forms with support for providing server-side validation errors to the fields within it.

  • Accessible – Uses a native <form> element, with support for ARIA labelling to create a form landmark.
  • Validation – Support for native HTML constraint validation with customizable UI, custom validation functions, realtime validation, and server-side validation errors.

See the Forms guide to learn more about React Aria's form components, including submitting data, and form validation techniques.

Anatomy

A form consists of a container element that includes a group of input elements, typically with a button the user can press to submit data to a server. Forms may also include validation error messages, and a button to reset the form data to its initial state.

If a form has an aria-label or aria-labelledby attribute, it is exposed to assistive technology as a form landmark, allowing users to quickly navigate to it.

tsx
import {Form, Button} from 'react-aria-components';

<Form>
  <Button type="submit" />
  <Button type="reset" />
</Form>

Starter kits

To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured Storybook that you can experiment with, or use as a starting point for your own component library.

<StarterKits component="form" />

Events

The onSubmit event will be triggered when a user submits the form with the <Keyboard>Enter</Keyboard> key or by pressing a submit button. The onReset event will be triggered when a user presses a reset button.

tsx
function Example() {
  let [action, setAction] = React.useState(null);
  return (
    <Form
      /*- begin highlight -*/
      onSubmit={e => {
        e.preventDefault();
        let data = Object.fromEntries(new FormData(e.currentTarget));
        setAction(`submit ${JSON.stringify(data)}`);
      }}
      onReset={() => setAction('reset')}
      /*- end highlight -*/
    >
      <TextField name="username" isRequired>
        <Label>Username</Label>
        <Input />
        <FieldError />
      </TextField>
      <TextField name="password" type="password" isRequired>
        <Label>Password</Label>
        <Input />
        <FieldError />
      </TextField>
      <div style={{display: 'flex', gap: 8}}>
        <Button type="submit">Submit</Button>
        <Button type="reset">Reset</Button>
      </div>
      {action && <div>Action: <code>{action}</code></div>}
    </Form>
  );
}

Validation

React Aria supports native HTML constraint validation with customizable UI, custom validation functions, realtime validation, and integration with server-side validation errors. The Form component facilitates server-side validation by providing error messages to the fields within it.

To provide validation errors, the validationErrors prop should be set to an object that maps each field's name prop to a string or array of strings representing one or more errors. These are displayed to the user as soon as the validationErrors prop is set, and cleared after the user modifies each field's value.

tsx
<Form validationErrors={{username: 'Sorry, this username is taken.'}}>
  <TextField name="username">
    <Label>Username</Label>
    <Input />
    <FieldError />
  </TextField>
</Form>

See the Forms guide to learn more about form validation in React Aria, including client-side validation, and integration with other frameworks and libraries.

Validation behavior

By default, native HTML form validation is used to display errors and block form submission. To instead use ARIA attributes for form validation, set the validationBehavior prop to "aria". This will not block form submission, and will display validation errors to the user in realtime as the value is edited.

The validationBehavior can be set at the form level to apply to all fields, or at the field level to override the form's behavior for a specific field.

tsx
<Form validationBehavior="aria">
  <TextField
    name="username"
    defaultValue="admin"
    isRequired
    validate={value => value === 'admin' ? 'Nice try.' : null}>
    <Label>Username</Label>
    <Input />
    <FieldError />
  </TextField>
  <Button type="submit">Submit</Button>
</Form>

Focus management

By default, after a user submits a form with validation errors, the first invalid field will be focused. You can prevent this by calling preventDefault during the onInvalid event, and move focus yourself. This example shows how to move focus to an alert element at the top of a form.

tsx
function Example() {
  let [isInvalid, setInvalid] = React.useState(false);

  return (
    <Form
      /*- begin highlight -*/
      onInvalid={e => {
        e.preventDefault();
        setInvalid(true);
      }}
      /*- end highlight -*/
      onSubmit={e => {
        e.preventDefault();
        setInvalid(false);
      }}
      onReset={() => setInvalid(false)}>
      {isInvalid &&
        /*- begin highlight -*/
        <div role="alert" tabIndex={-1} ref={e => e?.focus()}>
          <h3>Unable to submit</h3>
          <p>Please fix the validation errors below, and re-submit the form.</p>
        </div>
      }
      <TextField name="firstName" isRequired>
        <Label>First Name</Label>
        <Input />
        <FieldError />
      </TextField>
      <TextField name="lastName" isRequired>
        <Label>Last Name</Label>
        <Input />
        <FieldError />
      </TextField>
      <div style={{display: 'flex', gap: 8}}>
        <Button type="submit">Submit</Button>
        <Button type="reset">Reset</Button>
      </div>
    </Form>
  );
}
<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
css
.react-aria-Form [role=alert] {
  border: 2px solid var(--invalid-color);
  background: var(--overlay-background);
  border-radius: 6px;
  padding: 12px;
  max-width: 250px;
  outline: none;

  &:focus-visible {
    outline: 2px solid var(--focus-ring-color);
    outline-offset: 2px;
  }

  h3 {
    margin-top: 0;
  }

  p {
    margin-bottom: 0;
  }
}
</details>

Props

<PropTable component={docs.exports.Form} links={docs.links} />

Styling

React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName naming convention.

css
.react-aria-Form {
  /* ... */
}

A custom className can also be specified on any component. This overrides the default className provided by React Aria with your own.

jsx
<Form className="my-form">
</Form>

Advanced customization

Contexts

All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in mergeProps).

<ContextTable components={['Form']} docs={docs} />

This example adds a global form submission handler for all forms rendered inside it, which could be used to centralize logic to submit data to an API.

tsx
let onSubmit = e => {
  e.preventDefault();
  // Submit form data to an API...
};

<FormContext.Provider value={{onSubmit}}>
  <Form>
  </Form>
</FormContext.Provider>

FormContext can also be used within any component inside a form to access props from the nearest ancestor form. For example, to access the current validationBehavior, use the useSlottedContext hook.

tsx
import {FormContext, useSlottedContext} from 'react-aria-components';

function MyFormField() {
  let {validationBehavior} = useSlottedContext(FormContext);
  // ...
}

<Form validationBehavior="aria">
  <MyFormField />
</Form>

Validation context

The Form component provides a value for FormValidationContext, which allows child elements to receive validation errors from the form. You can provide a value for this context directly in case you need to customize the form element, or reuse an existing form component.

tsx
import {FormValidationContext} from 'react-aria-components';

<form>
  <FormValidationContext.Provider value={{username: 'Sorry, this username is taken.'}}>
    <TextField name="username">
      <Label>Username</Label>
      <Input />
      <FieldError />
    </TextField>
  </FormValidationContext.Provider>
</form>

Custom children

You can also consume FormValidationContext in your own custom form input components to receive validation errors. This example shows a native <select> that displays validation errors provided by Form.

tsx
import type {SelectHTMLAttributes} from 'react';
import {useContext} from 'react';
import {useId} from 'react-aria';

function NativeSelect(props: SelectHTMLAttributes<HTMLSelectElement> & {label: string}) {
  let errors = useContext(FormValidationContext);
  let error = errors?.[props.name];
  let id = useId();
  let descriptionId = useId();

  return (
    <div className="flex">
      <label htmlFor={id}>{props.label}</label>
      <select {...props} id={id} aria-describedby={descriptionId} />
      <small className="invalid" id={descriptionId}>{error}</small>
    </div>
  );
}

<Form validationErrors={{frequency: 'Please select a frequency.'}}>
  <NativeSelect label="Frequency" name="frequency">
    <option value="">Select an option...</option>
    <option>Always</option>
    <option>Sometimes</option>
    <option>Never</option>
  </NativeSelect>
</Form>
css
.flex {
  display: flex;
  flex-direction: column;
  gap: 4px;

  .invalid {
    margin: 0;
  }
}