Form Field
Form Field allows users to wrap input components to make them accessible and adds complementary labels and error messages.
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-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 {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-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 {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
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 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:trueDefines if a group like RadioGroup is required.
Choose your pizza options
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,
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 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 {
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
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 theisHiddenprop onFormField.Labelif 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.
| 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.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.
| 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. |
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
useFormFieldModel
useFormFieldModel (config: ): Specifications
Content Guidelines
- Reference the Form pattern for more information on designing forms and strategies to enable the best user experience of form content.
- For content guidance regarding input types for forms, see Checkbox, Menus, Empty State, Errors and Alerts, Field Labels, and Radio Buttons in the Content Style Guide.
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.