Workday Canvas

Form Field

Form Field allows users to wrap input components to make them accessible and adds complementary labels and error messages.

v14.2.34
Install
yarn add @workday/canvas-kit-react

Usage Guidance

The Form Field is a wrapper component intended to be used with Canvas Kit input components when designing a form to ensure they meet accessibility standards. Inputs collect data from users within a form. It’s important to choose the right input to elicit a response in the format you want. For more information on best practices for designing forms, reference the Form pattern.

When to Use

  • Use the Form Field component as a wrapper for most inputs on a form. From a designer perspective, it is not a visible component but something to be aware of when working with developers on a form as the Canvas Kit Form Field should encapsulate input components on a form.
  • Use form fields to ensure forms meet accessibility guidelines.

When to Use Something Else

  • Consider using a Table when presenting and editing sets of repeating data with the same structure.
  • Consider using a Popup or Toast component to display confirmation messages or validate user inputs in the context of a user action.

Examples

Basic

Form Field should be used in tandem with most Canvas Kit input components to ensure they meet accessibility standards. The orientation of the label by default is vertical.

import React from 'react';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {Flex} from '@workday/canvas-kit-react/layout';
import {TextInput} from '@workday/canvas-kit-react/text-input';

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <Flex>
      <FormField>
        <FormField.Label>First Name</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} value={value} onChange={handleChange} />
        </FormField.Field>
      </FormField>
    </Flex>
  );
};

Error States

Set the error prop of the Form Field or define it in the model to indicate it has an error. error accepts the following values:

"error" | "caution" | undefined

Caution

Use the caution state when a value is valid but there is additional information.

Alert: Password strength is weak, using more characters is recommended.

import React from 'react';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const [value, setValue] = React.useState('hi');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <Flex>
      <FormField error="caution">
        <FormField.Label>Create Password</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} type="password" value={value} onChange={handleChange} />
          <FormField.Hint>
            Alert: Password strength is weak, using more characters is recommended.
          </FormField.Hint>
        </FormField.Field>
      </FormField>
    </Flex>
  );
};

Accessibility Note: Caution state will not include the aria-invalid attribute on the input for screen readers. Use error states when values are not valid.

Error

Use the error state when the value is no longer valid.

Error: Must Contain a number and a capital letter

import React from 'react';
import {FormField} from '@workday/canvas-kit-react/form-field';

import {TextInput} from '@workday/canvas-kit-react/text-input';
import {Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <Flex>
      <FormField error="error">
        <FormField.Label>Password</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} type="password" value={value} onChange={handleChange} />
          <FormField.Hint>Error: Must Contain a number and a capital letter</FormField.Hint>
        </FormField.Field>
      </FormField>
    </Flex>
  );
};

Accessibility Note: Error states include visual color changes to the input border and require supplemental “error” text for colorblind users to distinguish between fields in an error state from fields with standard hint text. Read more about Failure of Success Criterion 1.4.1 due to identifying required or error fields using color differences only

Hint

Use FormField.Hint to display a short message below the input component and FormField.Field to ensure proper alignment.

Cannot contain numbers

import React from 'react';
import {FormField} from '@workday/canvas-kit-react/form-field';

import {TextInput} from '@workday/canvas-kit-react/text-input';
import {Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <Flex>
      <FormField orientation="horizontalStart">
        <FormField.Label>First Name</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} value={value} onChange={handleChange} />
          <FormField.Hint>Cannot contain numbers</FormField.Hint>
        </FormField.Field>
      </FormField>
    </Flex>
  );
};

Accessibility Note: Hints are automatically associated to the input field with the aria-describedby attribute. This ensures that screen readers can automatically announce the hint text to users when the input field is focused.

Disabled

Set the disabled prop of FormField.Input to prevent users from interacting with it.

import React from 'react';
import {FormField} from '@workday/canvas-kit-react/form-field';

import {TextInput} from '@workday/canvas-kit-react/text-input';
import {Flex} from '@workday/canvas-kit-react/layout';

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <Flex>
      <FormField>
        <FormField.Label>Email</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} value={value} disabled onChange={handleChange} />
        </FormField.Field>
      </FormField>
    </Flex>
  );
};

Accessibility Note: Disabled form elements are exempt from WCAG minimum contrast guidelines. Despite this exemption, disabled fields are more difficult for low vision and colorblind users to perceive and may harm the usability of the form. Consider using text elements instead, or read-only fields if users cannot modify data.

Label Position

Set the orientation prop of the Form Field to designate the position of the label relative to the input component. By default, the orientation will be set to vertical. If you want your label to be horizontal, you have two options: horizontalStart and horizontalEnd.

If you want the position of the label at the start of the container, set orientation prop to horizontalStart.

import React from 'react';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const formStyles = createStyles({
  display: 'flex',
  gap: system.space.x2,
  flexDirection: 'column',
});

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <form className={formStyles}>
      <FormField orientation="horizontalStart">
        <FormField.Label>Email</FormField.Label>
        <FormField.Input as={TextInput} value={value} onChange={handleChange} />
      </FormField>
      <FormField orientation="horizontalStart">
        <FormField.Label>Password</FormField.Label>
        <FormField.Input as={TextInput} type="password" />
      </FormField>
    </form>
  );
};

If you want the position of the label at the end of the container, set orientation prop to horizontalEnd.

import React from 'react';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const formStyles = createStyles({
  display: 'flex',
  gap: system.space.x2,
  flexDirection: 'column',
});

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <form className={formStyles}>
      <FormField orientation="horizontalEnd">
        <FormField.Label>Email</FormField.Label>
        <FormField.Input as={TextInput} value={value} onChange={handleChange} />
      </FormField>
      <FormField orientation="horizontalEnd">
        <FormField.Label>Password</FormField.Label>
        <FormField.Input as={TextInput} type="password" />
      </FormField>
    </form>
  );
};

Grow

Set the grow prop of the Form Field to true to configure it (including the wrapped input component) to expand to the width of its container.

Note: This Prop is deprecated and will be removed in a future major version.

import React from 'react';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {Flex} from '@workday/canvas-kit-react/layout';
import {TextInput} from '@workday/canvas-kit-react/text-input';

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <Flex>
      <FormField grow>
        <FormField.Label>First Name</FormField.Label>
        <FormField.Input as={TextInput} value={value} onChange={handleChange} />
      </FormField>
    </Flex>
  );
};

Ref Forwarding

If you need full customization you can use the FormField behavior hooks to build your own solution. It is also easy it work with custom components or third party libraries and get the CKR accessibility guarantees by using the as prop.

import React from 'react';
import {changeFocus} from '@workday/canvas-kit-react/common';
import {Flex} from '@workday/canvas-kit-react/layout';
import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const parentContainerStyles = createStyles({
  gap: system.space.x1,
  alignItems: 'flex-start',
  flexDirection: 'column',
});

export default () => {
  const [value, setValue] = React.useState('');
  const ref = React.useRef(null);

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  const handleClick = () => {
    changeFocus(ref.current);
  };

  return (
    <Flex cs={parentContainerStyles}>
      <FormField>
        <FormField.Label>Email</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} onChange={handleChange} value={value} ref={ref} />
        </FormField.Field>
      </FormField>
      <SecondaryButton onClick={handleClick}>Focus Text Input</SecondaryButton>
    </Flex>
  );
};

Required

Set the isRequired prop of the Form Field to true to indicate that the field is required. Labels for required fields are suffixed by a red asterisk.

import React from 'react';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {FormField} from '@workday/canvas-kit-react/form-field';

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <FormField isRequired={true}>
      <FormField.Label>Email</FormField.Label>
      <FormField.Field>
        <FormField.Input
          as={TextInput}
          placeholder="your@gmail.com"
          onChange={handleChange}
          value={value}
        />
      </FormField.Field>
    </FormField>
  );
};

Accessibility Note: The HTML required attribute will be added to the input field and announced by screen readers. Consider adding a note at the top of your form indicating that fields marked with an asterisk (*) are required. This provides context for all users.

Grouped Inputs

Use FormFieldGroup when you have a group of inputs that need to be associated to one another, like RadioGroup or a group of Checkbox’s. FormFieldGroup renders a fieldset element and FormFieldGroup.Label renders a legend element. These elements will allow screen readers to automatically announce the legend’s context when focusing on the inputs in the group.

FormFieldGroup supports the same props of FormField:

  • error: "caution" | "error" Defines the error around the whole group of inputs.
  • orientation: "horizontal" | "vertical" Defines the legend placement.
  • isRequired: true Defines if a group like RadioGroup is required.

Choose your pizza options

Choose Your Toppings
Choose Your Crust
Selected Toppings:
Selected Crust:
import React from 'react';
import {FormFieldGroup} from '@workday/canvas-kit-react/form-field';
import {PrimaryButton, SecondaryButton} from '@workday/canvas-kit-react/button';
import {system} from '@workday/canvas-tokens-web';
import {Banner} from '@workday/canvas-kit-react/banner';

import {Checkbox} from '@workday/canvas-kit-react/checkbox';
import {RadioGroup} from '@workday/canvas-kit-preview-react/radio';
import {createStyles} from '@workday/canvas-kit-styling';
import {AriaLiveRegion} from '@workday/canvas-kit-react/common';

const formStyles = createStyles({
  margin: `${system.space.zero} ${system.space.x3}`,
});

const formButtonStyles = createStyles({
  display: 'inline-flex',
  gap: system.space.x2,
});

const toppings = [
  {
    id: 1,
    label: 'Pepperoni',
    checked: false,
  },
  {
    id: 2,
    label: 'Cheese',
    checked: false,
  },
  {
    id: 3,
    label: 'Pineapple',
    checked: false,
  },
  {
    id: 4,
    label: 'Mushrooms',
    checked: false,
  },
];

const bannerStyles = createStyles({
  position: 'absolute',
  right: 0,
});

export default () => {
  const [toppingsState, setToppingsState] = React.useState(toppings);
  const [error, setError] = React.useState(undefined);
  const [radioError, setRadioError] = React.useState(undefined);
  const [showSuccess, setShowSuccess] = React.useState(false);

  const [value, setValue] = React.useState<string>('');
  const [formData, setFormData] = React.useState({
    toppings: [],
    crust: '',
  });
  const handleCheckboxCheck = id => {
    if (error) {
      setError(undefined);
    }
    setToppingsState(
      toppingsState.map(item => (item.id === id ? {...item, checked: !item.checked} : item))
    );
  };

  const handleRadioChange = (e: React.ChangeEvent) => {
    if (radioError) {
      setRadioError(undefined);
    }
    const target = e.currentTarget;
    if (target instanceof HTMLInputElement) {
      setValue(target.value);
    }
  };

  const handleSubmit = e => {
    e.preventDefault();
    const radioError = !value && toppingsState.some(item => !item.checked) ? 'error' : undefined;
    const error = toppingsState.every(item => !item.checked) ? 'error' : undefined;

    setRadioError(radioError);
    setError(error);
    if (!error && !radioError && toppingsState.some(item => item.checked) && value) {
      setShowSuccess(true);
    }
    setFormData({
      toppings: toppingsState,
      crust: value,
    });
  };

  React.useEffect(() => {
    const timeout = setTimeout(() => {
      if (showSuccess) {
        setShowSuccess(false);
      }
    }, 3000);

    return () => clearTimeout(timeout);
  }, [showSuccess]);

  const handleReset = () => {
    setFormData({toppings: [], crust: ''});
    setError(undefined);
    setValue('');
    setRadioError('');
    setShowSuccess(false);
    setToppingsState(
      toppingsState.map(item => {
        return {...item, checked: false};
      })
    );
  };

  return (
    <div>
      <h3>Choose your pizza options</h3>
      <AriaLiveRegion role="alert">
        <div style={{display: 'flex', gap: '40px'}}>
          {error || radioError ? (
            <Banner isSticky hasError className={bannerStyles}>
              <Banner.Label>
                {error && radioError
                  ? 'At least one topping and crust selection is required'
                  : error
                  ? 'You must choose at least one topping'
                  : radioError
                  ? 'You must choose a crust'
                  : ''}
              </Banner.Label>
            </Banner>
          ) : null}
          {showSuccess && (
            <Banner isSticky className={bannerStyles}>
              <Banner.Label>You've successfully submitted your pizza options.</Banner.Label>
            </Banner>
          )}
        </div>
      </AriaLiveRegion>

      <form className={formStyles} onSubmit={handleSubmit}>
        <FormFieldGroup error={error} isRequired>
          <FormFieldGroup.Label>Choose Your Toppings</FormFieldGroup.Label>
          <FormFieldGroup.List>
            {toppingsState.map(item => {
              return (
                <FormFieldGroup.Input
                  key={item.id}
                  onChange={() => handleCheckboxCheck(item.id)}
                  checked={item.checked}
                  value={item.label}
                  as={Checkbox}
                  disabled={item.label === 'Pineapple' ? true : undefined}
                  label={item.label}
                />
              );
            })}
          </FormFieldGroup.List>
          <FormFieldGroup.Hint>
            {error === 'error' && 'Error: You must choose one topping'}
          </FormFieldGroup.Hint>
        </FormFieldGroup>
        <FormFieldGroup error={radioError} isRequired>
          <FormFieldGroup.Label>Choose Your Crust</FormFieldGroup.Label>
          <FormFieldGroup.Field>
            <FormFieldGroup.List
              as={RadioGroup}
              onChange={handleRadioChange}
              value={value}
              name="crust"
            >
              <FormFieldGroup.Input as={RadioGroup.RadioButton} value="thin-crust">
                Thin Crust
              </FormFieldGroup.Input>
              <FormFieldGroup.Input as={RadioGroup.RadioButton} value="hand-tossed">
                Hand Tossed
              </FormFieldGroup.Input>
              <FormFieldGroup.Input as={RadioGroup.RadioButton} value="deep-dish">
                Deep Dish
              </FormFieldGroup.Input>
              <FormFieldGroup.Input as={RadioGroup.RadioButton} value="cauliflower">
                Cauliflower
              </FormFieldGroup.Input>
            </FormFieldGroup.List>
            <FormFieldGroup.Hint>
              {radioError === 'error' ? 'Error: You must choose a crust' : null}
            </FormFieldGroup.Hint>
          </FormFieldGroup.Field>
        </FormFieldGroup>
        <div className={formButtonStyles}>
          <PrimaryButton type="submit">Submit Your Choices</PrimaryButton>
          <SecondaryButton onClick={() => handleReset()}>Reset Form</SecondaryButton>
        </div>
      </form>
      <div>
        <div>
          Selected Toppings:{' '}
          {!error && formData.toppings.map(item => (item.checked ? `${item.label} ` : null))}
        </div>
        <div>Selected Crust: {formData.crust}</div>
      </div>
    </div>
  );
};

Accessibility Note: In addition to radio button and checkbox groups, FormFieldGroup can be useful in any situation where the form needs to have multiple sets of identical input fields. For example, a form with identical fields for a Shipping address and a Billing address. The legend provides critical context for screen reader users in these situations.

Custom

If you need full customization you can use the FormField behavior hooks to build your own solution. It is also easy it work with custom components or third party libraries and get the CKR accessibility guarantees by using the as prop.

You can be anything
import React from 'react';
import {
  useFormFieldHint,
  useFormFieldInput,
  useFormFieldLabel,
  useFormFieldModel,
  formFieldStencil,
} from '@workday/canvas-kit-react/form-field';
import {useModelContext} from '@workday/canvas-kit-react/common';
import {Flex} from '@workday/canvas-kit-react/layout';

const Label = ({model, children}) => {
  const localModel = useModelContext(useFormFieldModel.Context, model);
  const props = useFormFieldLabel(localModel);

  return (
    <label {...props}>
      {children}
      {model.state.isRequired ? '*' : ''}
    </label>
  );
};

const Hint = ({model, children}) => {
  const localModel = useModelContext(useFormFieldModel.Context, model);
  const props = useFormFieldHint(localModel);

  return <span {...props}>{children}</span>;
};

const Input = ({model, ...elementProps}) => {
  const localModel = useModelContext(useFormFieldModel.Context, model);
  const props = useFormFieldInput(localModel, elementProps);

  return <input type="text" required={model.state.isRequired ? true : false} {...props} />;
};

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  const model = useFormFieldModel({isRequired: true});

  return (
    <Flex cs={formFieldStencil({orientation: 'horizontalStart'})}>
      <Label model={model}>My Custom Field</Label>
      <Input model={model} value={value} onChange={handleChange} />
      <Hint model={model}>You can be anything</Hint>
    </Flex>
  );
};

Custom id

Form Field will automatically generate an HTML id for its input element to link it to the correponding label. Alternatively, you may set the id prop of the Form Field to specify a custom id for the input element. The id will be appended by input-${your-unique-id}.

import React from 'react';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {Flex} from '@workday/canvas-kit-react/layout';
import {TextInput} from '@workday/canvas-kit-react/text-input';

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <Flex>
      <FormField id="first-name">
        <FormField.Label>First Name</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} value={value} onChange={handleChange} />
        </FormField.Field>
      </FormField>
    </Flex>
  );
};

All Fields

Form Field should allow you to use it with all inputs including Select, TextInput, Checkbox, TextArea, Switch, and RadioGroup.

Hint text for your input

Radio Group Legend

Error Message

Checkbox Legend
Choose Your Crust
Checkbox Legend
import React from 'react';
import {FormField, FormFieldGroup} from '@workday/canvas-kit-react/form-field';
import {Flex} from '@workday/canvas-kit-react/layout';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {RadioGroup} from '@workday/canvas-kit-preview-react/radio';
import {Checkbox} from '@workday/canvas-kit-react/checkbox';
import {Select} from '@workday/canvas-kit-react/select';
import {TextArea} from '@workday/canvas-kit-react/text-area';
import {Switch} from '@workday/canvas-kit-react/switch';
import {calc, createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const parentContainerStyles = createStyles({
  flexDirection: 'column',
  gap: calc.subtract(system.space.x6, system.space.x1),
  padding: calc.subtract(system.space.x10, system.space.x1),
  borderRadius: system.space.x1,
});

export default () => {
  return (
    <Flex cs={parentContainerStyles}>
      <FormField grow>
        <FormField.Label>First Name</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} />
        </FormField.Field>
      </FormField>

      <FormField isRequired={true} error="caution" grow>
        <FormField.Label>Email</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} />
          <FormField.Hint>Hint text for your input</FormField.Hint>
        </FormField.Field>
      </FormField>
      <FormField grow>
        <FormField.Label>Text Area Label</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextArea} />
        </FormField.Field>
      </FormField>
      <FormField error="error" grow>
        <FormField.Label>Choose a Crust</FormField.Label>
        <Select items={['Pizza', 'Cheeseburger', 'Fries']}>
          <FormField.Input as={Select.Input} />
          <Select.Popper>
            <Select.Card>
              <Select.List>{item => <Select.Item>{item}</Select.Item>}</Select.List>
            </Select.Card>
          </Select.Popper>
        </Select>
      </FormField>
      <FormField as="fieldset" isRequired={true} error={'error'} orientation="horizontalStart" grow>
        <FormField.Label as="legend">Radio Group Legend</FormField.Label>
        <FormField.Container>
          <FormField.Input as={RadioGroup}>
            <RadioGroup.RadioButton value="deep-dish">Deep dish</RadioGroup.RadioButton>
            <RadioGroup.RadioButton value="thin">Thin</RadioGroup.RadioButton>
            <RadioGroup.RadioButton value="gluten-free">Gluten free</RadioGroup.RadioButton>
            <RadioGroup.RadioButton value="cauliflower">Cauliflower</RadioGroup.RadioButton>
            <RadioGroup.RadioButton value="butter">
              Butter - the best thing to put on bread
            </RadioGroup.RadioButton>
          </FormField.Input>
          <FormField.Hint>Error Message</FormField.Hint>
        </FormField.Container>
      </FormField>
      <FormField as="fieldset" grow>
        <FormField.Label as="legend">Checkbox Legend</FormField.Label>
        <FormField.Input checked={true} as={Checkbox} label="Checkbox Label" />
        <FormField.Input checked={false} as={Checkbox} label="Thin Crust" />
        <FormField.Input checked={false} as={Checkbox} label="Extra Cheese" />
      </FormField>
      <FormFieldGroup error="error" orientation="horizontalStart" grow>
        <FormFieldGroup.Label>Choose Your Crust</FormFieldGroup.Label>
        <FormFieldGroup.Field>
          <FormFieldGroup.List as={RadioGroup}>
            <FormFieldGroup.Input as={RadioGroup.RadioButton} value="thin-crust">
              Thin Crust
            </FormFieldGroup.Input>
            <FormFieldGroup.Input as={RadioGroup.RadioButton} value="hand-tossed">
              Hand Tossed
            </FormFieldGroup.Input>
            <FormFieldGroup.Input as={RadioGroup.RadioButton} value="deep-dish">
              Deep Dish
            </FormFieldGroup.Input>
            <FormFieldGroup.Input as={RadioGroup.RadioButton} value="cauliflower">
              Cauliflower
            </FormFieldGroup.Input>
          </FormFieldGroup.List>
        </FormFieldGroup.Field>
      </FormFieldGroup>
      <FormFieldGroup grow>
        <FormFieldGroup.Label>Checkbox Legend</FormFieldGroup.Label>
        <FormField.Field>
          <FormFieldGroup.List>
            <FormFieldGroup.Input checked={true} as={Checkbox} label="Checkbox Label" />
            <FormFieldGroup.Input checked={false} as={Checkbox} label="Thin Crust" />
            <FormFieldGroup.Input checked={false} as={Checkbox} label="Extra Cheese" />
          </FormFieldGroup.List>
        </FormField.Field>
      </FormFieldGroup>

      <FormField orientation="horizontalStart" grow>
        <FormField.Label>Switch Label</FormField.Label>
        <FormField.Field>
          <FormField.Input as={Switch} />
        </FormField.Field>
      </FormField>
    </Flex>
  );
};

Hidden Label

In cases where you want to hide the label while still meeting accessibility standards, you can add isHidden on the <FormField.Label/>. This prop will visually hide the label.

import React from 'react';
import {
  FormField,
  useFormFieldModel,
  useFormFieldInput,
} from '@workday/canvas-kit-react/form-field';
import {Flex} from '@workday/canvas-kit-react/layout';
import {TextInput, InputGroup} from '@workday/canvas-kit-react/text-input';
import {SystemIcon} from '@workday/canvas-kit-react/icon';
import {searchIcon} from '@workday/canvas-system-icons-web';

/**
 * Using `as={InputGroup}` on `FormField.Input` will break the label associations necessary for accessibility.
 * In this example, we've rendered `FormField.Field` as `InputGroup` and then hoisted the `id` of the input from the FormField model.
 * This allows us to set the `id` of the `InputGroup.Input` correctly for proper label association.
 */

export default () => {
  const [value, setValue] = React.useState('');
  const model = useFormFieldModel();
  const {id: formFieldInputId} = useFormFieldInput(model);

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  return (
    <Flex>
      <FormField model={model}>
        <FormField.Label isHidden>Search</FormField.Label>
        <FormField.Field as={InputGroup}>
          <InputGroup.InnerStart>
            <SystemIcon icon={searchIcon} size="small" />
          </InputGroup.InnerStart>
          <InputGroup.Input
            as={TextInput}
            id={formFieldInputId}
            onChange={handleChange}
            value={value}
          />
        </FormField.Field>
      </FormField>
    </Flex>
  );
};

Accessibility Note: Hidden labels are typically not recommended. In this example, a universally recognizable icon like a magnifying glass signaling “search” may be a suitable alternative to visible text labels.

Themed Errors

You can theme your error rings by wrapping an input in a CanvasProvider and defining focusOutline and error properties on the theme.

Please enter an email.

import React from 'react';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {CanvasProvider, PartialEmotionCanvasTheme} from '@workday/canvas-kit-react/common';
import {colors, space} from '@workday/canvas-kit-react/tokens';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const formFieldHintStyles = createStyles({
  paddingTop: system.space.x2,
});

export default () => {
  const [value, setValue] = React.useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  const theme: PartialEmotionCanvasTheme = {
    canvas: {
      palette: {
        common: {
          focusOutline: colors.grapeSoda300,
        },
        error: {
          main: colors.islandPunch400,
        },
      },
    },
  };

  return (
    <CanvasProvider theme={theme}>
      <FormField error={!value ? 'error' : undefined} isRequired={true} orientation="vertical">
        <FormField.Label>Email</FormField.Label>
        <FormField.Field>
          <FormField.Input as={TextInput} onChange={handleChange} value={value} />
          <FormField.Hint cs={formFieldHintStyles}>
            {!value && 'Please enter an email.'}
          </FormField.Hint>
        </FormField.Field>
      </FormField>
    </CanvasProvider>
  );
};

Custom Styles

Form Field and its subcomponents support custom styling via the cs prop. For more information, check our “How To Customize Styles”.

Accessibility

FormField provides essential accessibility features to ensure form inputs are properly labeled and described for all users, including those using assistive technologies. This section covers both the technical implementation and best practices for creating accessible forms.

Label Association

The FormField adds a for attribute to the FormField.Label (<label> element) element that matches the id attribute of the FormField.Input which is usually a input element. This both labels the input for screen readers and other assistive technology as well as will focus on the input when the user clicks on the label. If your form field input component is more complicated, the FormField will also add an id to the FormField.Label and an aria-labelledby to the FormField.Input component. You can then forward the aria-labelledby to whatever elements you need for the proper accessibility.

For example, the DOM will look something like this:

<div> <label id="label-abc" for="input-abc">First Name</label> <input id="input-abc" aria-labelledby="label-abc" /> </div>

Some components, like MultiSelect, have an additional role=listbox element that also needs to link to the label element. The resulting DOM will look something like:

<div> <label id="label-abc" for="input-abc">States you've lived in</label> <input id="input-abc" aria-labelledby="label-abc" role="combobox" ... /> <ul role="listbox" aria-labelledby="label-abc"> <li>Texas</li> <li>California</li> </ul> </div>

The MultiSelect component gets the aria-labelledby from the FormField.Input and forwards it to both the input[role=combobox] element and the ul[role=listbox] element so the screen reader knows the label for both is “States you’ve lived in”.

Label Text Best Practices

  • Be Clear and Concise: Labels should clearly describe the purpose of the input field.
  • Use Visible Labels Instead of Only Placeholders: Always provide a persistent and accessible label with FormField.Label. Do not rely solely on placeholder text, as it can disappear while typing and may not be accessible to assistive technologies. Use the isHidden prop on FormField.Label if a hidden label is required for visual design.

Screen Reader Experience

  • The label is announced when the input receives focus.
  • Required, disabled, and invalid statuses are announced automatically.
  • Help text and error messages are announced automatically when focused.
  • For grouped inputs, the group label (legend) is announced automatically when focused.

Component API

FormField

Use FormField to wrap input components to make them accessible. You can customize the field by passing in TextInput, Select, RadioGroup and other form elements to FormField.Input through the as prop.

<FormField>
   <FormField.Label>First Name</FormField.Label>
   <FormField.Input as={TextInput} value={value} onChange={(e) => console.log(e)} />
 </FormField>

Layout Component

FormField supports all props from thelayout component.

Props

Props extend from div. Changing the as prop will change the element interface.

Props extend from . If a model is passed, props from FormFieldModelConfig are ignored.

NameTypeDescriptionDefault
orientation 'vertical' 'horizontalStart' 'horizontalEnd'

The direction the child elements should stack. In v13, horizontal will be removed. Please use horizontalStart or horizontalEnd for horizontal alignment.

'vertical'
childrenReactNode
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
growboolean

True if the component should grow to its container's width. False otherwise.

asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

FormField.Input

FormField.Input will render any inputs passed to it via the as prop, including TextInput, Select, Switch, TextArea, RadioGroup or any custom input. FromField.Input will be associated with FormField.Label and FormField.Hint by a generated id. You can customize this id by passing id to FormField.

Note: If you pass in a custom input that is not as Canvas Kit input, you will have to handle the error prop, validation and styling. For a custom approach, reference our Custom storybook example.

 <FormField id='my-unique-id'>
   <FormField.Label>My Label Text</FormField.Label>
   <FormField.Input as={TextInput} onChange={(e) => console.log(e)} />
 <FormField>

Layout Component

FormField.Input supports all props from thelayout component.

Props

Props extend from input. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
childrenReact.ReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

input
refReact.Ref<R = input>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useFormFieldInput

Adds the necessary props to an Input component. Used by the FormField.Input subcomponent and other input type components

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  required:  true undefined;
  aria-invalid:  true undefined;
  aria-describedby:  string undefined;
  aria-labelledby:  string undefined;
  id:  string undefined;
  error:  'error' 'caution' undefined;
}

FormField.Label

FormField.Label will render a label element that has a matching id to the FormField.Input.

<FormField>
   <FormField.Label>First Name</FormField.Label>
   <FormField.Input as={TextInput} value={value} onChange={(e) => console.log(e)} />
 </FormField>

Layout Component

FormField.Label supports all props from thelayout component.

Props

Props extend from label. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
childrenReactNode

The text of the label.

isHiddenboolean

When true, will apply accessibleHide to the label. This is useful in cases where you still need accessibility but don't want to show the label, like a search input.

cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
variant 'error' 'hint' 'inverse'

Type variant token names: error, hint or inverse.

<Text variant="error" typeLevel="subtext.large">Error text</Text>
typeLevel
  'body.medium'
  'body.large'
  'body.small'
  'title.medium'
  'title.large'
  'title.small'
  'heading.medium'
  'heading.large'
  'heading.small'
  'subtext.medium'
  'subtext.large'
  'subtext.small'

Type token as string with level and size separated by dot. These values map to our Canvas type levels.

<Text typeLevel="body.small">Small body text</Text>
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

label
refReact.Ref<R = label>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useFormFieldLabel

Adds the necessary props to a Label component. Used by the FormField.Label subcomponent and other input type components

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  htmlFor: any;
  id: any;
}

FormField.Hint

FormField.Hint will render any additional information you want to provide to the FormField.Input. If you set the orientation prop to horizontal you should use FormField.Field to properly align the hint with your FormField.Input.

<FormField>
   <FormField.Label>First Name</FormField.Label>
   <FormField.Input as={TextInput} value={value} onChange={(e) => console.log(e)} />
   <FormField.Hint>This is your hint text</FormField.Hint>
 </FormField>

Layout Component

FormField.Hint supports all props from thelayout component.

Props

Props extend from p. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
typeLevel
  'body.medium'
  'body.large'
  'body.small'
  'title.medium'
  'title.large'
  'title.small'
  'heading.medium'
  'heading.large'
  'heading.small'
  'subtext.medium'
  'subtext.large'
  'subtext.small'

Type token as string with level and size separated by dot. These values map to our Canvas type levels.

<Text typeLevel="body.small">Small body text</Text>
variant 'error' 'hint' 'inverse'

Type variant token names: error, hint or inverse.

<Text variant="error" typeLevel="subtext.large">Error text</Text>
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
childrenReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

p
refReact.Ref<R = p>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useFormFieldHint

Adds the necessary props to a Hint component. Used by the FormField.Hint subcomponent and other input type components

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  id: any;
}

FormField.Container

FormField.Field allows you to properly center FormField.Label when the orientation is set to horizontal and there is hint text..

<FormField orientation="horizontalStart">
   <FormField.Label>First Name</FormField.Label>
   <FormField.Container>
     <FormField.Input as={TextInput} value={value} onChange={(e) => console.log(e)} />
     <FormField.Hint>This is your hint text</FormField.Hint>
   </FormField.Container>
 </FormField>

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
childrenReact.ReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

FormField.Field

FormField.Field allows you to customize container alignment and styles when wrapping your input and hint text.

<FormField orientation="horizontalStart">
   <FormField.Label>First Name</FormField.Label>
   <FormField.Field>
     <FormField.Input as={TextInput} value={value} onChange={(e) => console.log(e)} />
     <FormField.Hint>This is your hint text</FormField.Hint>
   </FormField.Field>
 </FormField>

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
childrenReact.ReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

Model

useFormFieldModel

useFormFieldModel (config: ):

Specifications

Content Guidelines

Can't Find What You Need?

Check out our FAQ section which may help you find the information you're looking for. For further information, contact the #ask-canvas-design or #ask-canvas-kitchannels on Slack.

On this Page: