Form Field
Form Field allows users to wrap input components to make them accessible and adds complementary labels and error messages.
Component Type
Input
Platform
Web
Component
Sana Canvas
Delivery Channels
Web
Version
16.1.7Experience Surfaces
Page Body Inline
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 {Flex} from '@workday/canvas-kit-react/layout';
import {TextInput} from '@workday/canvas-kit-react/text-input';
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-invalidattribute 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 {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 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 {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 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-describedbyattribute. 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 {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>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 {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const formStyles = createStyles({
display: 'flex',
gap: system.gap.sm,
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 {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const formStyles = createStyles({
display: 'flex',
gap: system.gap.sm,
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 {SecondaryButton} from '@workday/canvas-kit-react/button';
import {changeFocus} from '@workday/canvas-kit-react/common';
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';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const parentContainerStyles = createStyles({
gap: system.gap.xs,
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 {FormField} from '@workday/canvas-kit-react/form-field';
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 (
<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
requiredattribute 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 div with role="group" and an
aria-labelledby reference. FormFieldGroup.Label renders a div with an id referenced by the
aria-labelledby of the FormFieldGroup. Screen readers announce the group label when focusing
controls in the group.
FormFieldGroup supports the same props of FormField:
error:"caution" | "error"Defines the error around the whole group of inputs.orientation:"vertical" | "horizontalStart" | "horizontalEnd"Defines the group label placement.isRequired:trueDefines if a group like RadioGroup is required.
Choose your pizza options
import React from 'react';
import {RadioGroup} from '@workday/canvas-kit-preview-react/radio';
import {Banner} from '@workday/canvas-kit-react/banner';
import {PrimaryButton, SecondaryButton} from '@workday/canvas-kit-react/button';
import {Checkbox} from '@workday/canvas-kit-react/checkbox';
import {AriaLiveRegion} from '@workday/canvas-kit-react/common';
import {FormFieldGroup} from '@workday/canvas-kit-react/form-field';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
const formStyles = createStyles({
margin: `0 ${px2rem(12)}`,
});
const formButtonStyles = createStyles({
display: 'inline-flex',
gap: system.gap.sm,
});
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,
FormFieldGroupcan 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 (group label) 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.
import React from 'react';
import {useModelContext} from '@workday/canvas-kit-react/common';
import {
formFieldStencil,
useFormFieldHint,
useFormFieldInput,
useFormFieldLabel,
useFormFieldModel,
} from '@workday/canvas-kit-react/form-field';
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
import {RadioGroup} from '@workday/canvas-kit-preview-react/radio';
import {Checkbox} from '@workday/canvas-kit-react/checkbox';
import {FormField, FormFieldGroup} from '@workday/canvas-kit-react/form-field';
import {Flex} from '@workday/canvas-kit-react/layout';
import {Select} from '@workday/canvas-kit-react/select';
import {Switch} from '@workday/canvas-kit-react/switch';
import {TextArea} from '@workday/canvas-kit-react/text-area';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {calc, createStyles} from '@workday/canvas-kit-styling';
import {base, system} from '@workday/canvas-tokens-web';
const parentContainerStyles = createStyles({
flexDirection: 'column',
gap: calc.subtract(system.gap.lg, system.gap.xs),
padding: calc.subtract(base.size500, system.padding.xxs),
borderRadius: system.shape.sm,
});
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.Field>
<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.Field>
</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,
useFormFieldInput,
useFormFieldModel,
} from '@workday/canvas-kit-react/form-field';
import {SystemIcon} from '@workday/canvas-kit-react/icon';
import {Flex} from '@workday/canvas-kit-react/layout';
import {InputGroup, TextInput} from '@workday/canvas-kit-react/text-input';
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 {CanvasProvider, PartialEmotionCanvasTheme} from '@workday/canvas-kit-react/common';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {createStyles, cssVar} from '@workday/canvas-kit-styling';
import {base, system} from '@workday/canvas-tokens-web';
const formFieldHintStyles = createStyles({
paddingBlockStart: system.padding.xs,
});
export default () => {
const [value, setValue] = React.useState('');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
const theme: PartialEmotionCanvasTheme = {
canvas: {
palette: {
common: {
focusOutline: cssVar(base.green600),
},
error: {
main: cssVar(base.magenta700),
},
},
},
};
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 have a programmatically
determinable name, relationships, and instructions. The primary accessibility goal is to ensure
assistive technology users can identify every field, understand how to complete it, and hear hints,
errors, and required state when the form input receives focus.
Minimum Accessible Structure
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
<FormField>
<FormField.Label>Email</FormField.Label>
<FormField.Field>
<FormField.Input as={TextInput} />
<FormField.Hint>We'll never share your email.</FormField.Hint>
</FormField.Field>
</FormField>;Include FormField.Hint whenever there is hint or error text—the wiring slot for
aria-describedby exists by default (see Built-in Behaviors).
Built-in Behaviors
Canvas Kit applies these automatically when you compose FormField with its subcomponents. Do not
duplicate them in consuming code.
ARIA and DOM (applied by subcomponents):
FormField.Label:<label>withhtmlFormatching theidonFormField.Input(input-{id}). Clicking the label moves focus to the input.FormField.Input:id(input-{id}),aria-labelledby(label-{id}),aria-describedby(hint-{id}when anidexists),requiredwhenisRequired, andaria-invalid="true"whenerror="error". Caution (error="caution") does not setaria-invalid.FormField.Hint:id="hint-{id}"for description association.- Generated IDs:
label-{id},input-{id}, andhint-{id}from theFormFieldid(auto-generated or custom via theidprop). FormFieldGroup:role="group"witharia-labelledbyreferencingFormFieldGroup.Label.- Composite controls (for example,
MultiSelect) forwardaria-labelledbyto related sub-elements (combobox, listbox) so every part shares the same accessible name.
Implementation note on aria-describedby: FormField.Input always sets
aria-describedby="hint-{id}" when an id exists, even if FormField.Hint is not rendered. Always
render FormField.Hint when there is hint or error text. Omitting FormField.Hint leaves a
dangling aria-describedby reference.
Keyboard (standard form control behavior):
- Tab / Shift+Tab move focus to and from the input (native tab order)
- Clicking
FormField.Labelmoves focus to the associated input (native<label>behavior) - Checkbox, radio, and composite controls follow the wrapped input component’s own keyboard patterns
Screen reader expectations (when built-in behaviors are used as intended):
- On focus, assistive technology announces the field label and, when applicable: required state,
invalid state (
error="error"), and hint or error text viaaria-describedby - The Caution state is visual only —
aria-invalidis not set forerror="caution" - In a
FormFieldGroup, the group label is associated viaaria-labelledby; focusing a control in the group includes the group name in context - Disabled inputs are skipped in the tab order and may be announced as unavailable
For a simple field, the DOM looks like:
<div>
<label id="label-abc" for="input-abc">First Name</label>
<input id="input-abc" aria-labelledby="label-abc" />
</div>Some composite controls such as MultiSelect, have additional sub-elements that also need to be
linked to the FormField.Label. In the example below, the FormField.Label is applied to both the
input[role=combobox] element and the ul[role=listbox] element so that screen reader knows that
the label for both is the “States you’ve lived in”.
<div>
<label id="label-abc" for="input-abc">States you've lived in</label>
<input
id="input-abc"
aria-labelledby="label-abc"
role="combobox"
aria-expanded="false"
aria-autocomplete="list"
aria-controls="listbox-abc"
type="text"
/>
<ul id="listbox-abc" role="listbox" aria-labelledby="label-abc">
<li role="option">Texas</li>
<li role="option">California</li>
</ul>
</div>For a field in an error state, the error text is referenced using aria-describedby. The rendered
markup looks like:
<div>
<label id="label-abc" for="input-abc">Password</label>
<input
id="input-abc"
aria-labelledby="label-abc"
aria-describedby="hint-abc"
required
aria-invalid="true"
/>
<p id="hint-abc">Error: Must contain a number and a capital letter</p>
</div>For grouped checkbox controls, compose a FormFieldGroup with FormFieldGroup.Label and
FormFieldGroup.Input. The rendered markup looks like:
<div role="group" aria-labelledby="label-abc">
<div id="label-abc">Pizza toppings:</div>
<div>
<input type="checkbox" id="checkbox-1" />
<label for="checkbox-1">Pepperoni</label>
</div>
<div>
<input type="checkbox" id="checkbox-2" />
<label for="checkbox-2">Mushrooms</label>
</div>
<div>
<input type="checkbox" id="checkbox-3" />
<label for="checkbox-3">Pineapple</label>
</div>
</div>Accessibility Requirements
Required in application code for accessible form fields. Rows marked (conditional) apply only when the situation matches—otherwise omit.
If no design spec is provided: use a visible FormField.Label, wrap the input with
FormField.Input, omit isHidden, and omit a custom id unless testing or composition requires
it.
| Requirement | How to satisfy |
|---|---|
| Visible label | FormField.Label with concise, meaningful text |
| Input wiring | FormField.Input as={...} wrapping every Canvas Kit input (TextInput, Select, TextArea, etc.) |
| Hint or error text (conditional) | FormField.Hint when there is help or validation copy—required because aria-describedby is wired by default (see Built-in Behaviors) |
| Error state (conditional) | error="error" on FormField and FormField.Hint prefixed with “Error:” so users who cannot perceive color can distinguish errors from hints |
| Caution state (conditional) | error="caution" on FormField with descriptive FormField.Hint (no aria-invalid) |
| Required field (conditional) | isRequired on FormField; consider a form-level note that asterisk (*) marks required fields |
| Group label (conditional) | FormFieldGroup + FormFieldGroup.Label for radio buttons, checkbox groups, or related inputs. Alternatively, FormField as="fieldset" with FormField.Label as="legend" |
| Hidden label (conditional) | FormField.Label isHidden only when an equally clear alternative exists (for example, a universally recognizable search icon) |
| Stable IDs (conditional) | id prop on FormField when multiple fields share a page or predictable label-, input-, and hint- IDs are needed for testing or custom composition |
Summary for code generation:
- REQUIRED: visible label, input wiring
- CONDITIONAL: hint/error text, required state, group label, hidden label, stable
id
Anti-Patterns
Do not generate code that does the following (see Accessibility Requirements above for what to supply instead):
- Manually set
id,htmlFor,aria-labelledby,aria-describedby,aria-invalid, orrequiredonFormField.InputorFormField.Label— Canvas Kit subcomponents wire these automatically - Unlabeled inputs: Do not use Canvas Kit inputs without
FormFieldandFormField.Label. - Placeholder-only labels: Do not rely on
placeholderinstead ofFormField.Label; placeholders disappear while typing and are poor substitutes for labels. - Color-only errors: Do not set
error="error"without descriptive text inFormField.Hint. - Caution for invalid values: Do not use
error="caution"when a value is invalid; useerror="error"soaria-invalidis exposed. - Broken ID references: Do not point
aria-labelledbyoraria-describedbyat missing or empty elements; ensureFormField.LabelandFormField.Hintrender meaningful content. - Skipping group labels: Do not render
RadioGroupor multiple related checkboxes withoutFormFieldGroupandFormFieldGroup.Label(orFormFieldwithas="fieldset"andFormField.Label as="legend"). - Disabled when read-only fits: Avoid
disabledonFormField.Inputwhen users only need to view data; disabled fields are harder to perceive and are removed from the tab order.
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.
| Name | Type | Description | Default |
|---|---|---|---|
orientation | 'vertical' | 'horizontalStart' | 'horizontalEnd' | The direction the child elements should stack. In v13, | 'vertical' |
children | ReactNode | ||
cs | | The | |
grow | boolean | True if the component should grow to its container's width. False otherwise. | |
as | React.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 Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | div |
ref | React.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 | |
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 | ( | 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.
| Name | Type | Description | Default |
|---|---|---|---|
cs | | The | |
children | React.ReactNode | ||
as | React.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 Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | input |
ref | React.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 | |
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 | ( | 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.
| Name | Type | Description | Default |
|---|---|---|---|
children | ReactNode | The text of the label. | |
isHidden | boolean | When true, will apply | |
cs | | The | |
variant | 'error' | 'hint' | 'inverse' | Type variant token names: | |
typeLevel | | Type token as string with level and size separated by dot. These values map to our Canvas type levels. | |
as | React.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 Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | label |
ref | React.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 | |
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 | ( | 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.
| Name | Type | Description | Default |
|---|---|---|---|
typeLevel | | Type token as string with level and size separated by dot. These values map to our Canvas type levels. | |
variant | 'error' | 'hint' | 'inverse' | Type variant token names: | |
cs | | The | |
children | ReactNode | ||
as | React.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 Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | p |
ref | React.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 | |
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 | ( | 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.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.
| Name | Type | Description | Default |
|---|---|---|---|
cs | | The | |
children | React.ReactNode | ||
as | React.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 Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | div |
ref | React.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 | |
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 | ( | 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
Content Guidelines
- Reference the Form pattern for more information on designing forms and strategies to enable the best user experience of form content.