Text Input
Text Inputs allow users to enter words, numbers, or characters without styling.
Component Type
Input
Platform
Web
Component
Sana Canvas
Delivery Channels
Web
Version
16.1.7Experience Surfaces
Page Body Inline
Anatomy

- Label: Title of the Text Input.
- Input Container: Rectangular container that houses the placeholder and input text.
- Placeholder/Input Text: Placeholder text is optional and shows an example of how to format the text for what the input is used for.
Usage Guidance
- Text Inputs can only support words, numbers or characters.
- Standard and Wide Text Inputs does not support images or any text styling.
- To ensure we don’t overwhelm users, there shouldn’t be more than two Wide Text Inputs on a page.
- For all Text Inputs on Web, a user clicking into an input or label that is not disabled will trigger the text cursor to appear, allowing users the ability to type. As the user types in the Text Input, the placeholder text is replaced with the user’s input.
When to Use
- Text Input is typically a form element used to collect user data that includes words, numbers or characters.
When to Use Something Else
- If styling is needed, such as for configuring email messages, you can use a Rich Text Editor instead.
- Use a Text Area when you need to let users enter an amount of text that’s longer than a single line.
- Consider using a Select, Radio or Checkboxes if there are predetermined data that a user should not input themselves.
Examples
Basic Example
Text Input should be used in tandem with Form Field to ensure proper label association and screen reader support.
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>
<FormField.Label>Email</FormField.Label>
<FormField.Field>
<FormField.Input as={TextInput} onChange={handleChange} value={value} />
</FormField.Field>
</FormField>
);
};
Disabled
Set the disabled prop of the Text 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';
export default () => {
const [value, setValue] = React.useState('');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
return (
<FormField>
<FormField.Label>Email</FormField.Label>
<FormField.Field>
<FormField.Input as={TextInput} disabled onChange={handleChange} value={value} />
</FormField.Field>
</FormField>
);
};
Placeholder
Set the placeholder prop of the Text Input to display a sample of its expected format or value
before a value has been provided.
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>
<FormField.Label>Email</FormField.Label>
<FormField.Field>
<FormField.Input
as={TextInput}
onChange={handleChange}
placeholder="user@email.com"
value={value}
/>
</FormField.Field>
</FormField>
);
};
Accessibility Note: Always provide a persistent
FormField.Labeland never rely on placeholder text as the only label for an input. Placeholders can disappear or lack sufficient contrast. Use placeholders only for short format examples (e.g., “name@example.com”), and place detailed instructions or guidance inFormField.Hintinstead of the placeholder.
Ref Forwarding
Text Input supports ref forwarding . It will forward
ref to its underlying <input type="text"> element.
import React from 'react';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
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 ref = React.useRef(null);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
const handleClick = () => {
ref.current.focus();
};
return (
<>
<FormField>
<FormField.Label>Email</FormField.Label>
<FormField.Field>
<FormField.Input as={TextInput} onChange={handleChange} ref={ref} value={value} />
</FormField.Field>
</FormField>
<PrimaryButton onClick={handleClick}>Focus Text Input</PrimaryButton>
</>
);
};
Grow
Set the grow prop of the wrapping Form Field to true to configure the Text Input to expand to
the width of its container.
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 grow>
<FormField.Label>Street Address</FormField.Label>
<FormField.Field>
<FormField.Input as={TextInput} onChange={handleChange} value={value} />
</FormField.Field>
</FormField>
);
};
Label Position Horizontal
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.
Add a valid email
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 orientation="horizontalStart">
<FormField.Label>Email</FormField.Label>
<FormField.Field>
<FormField.Input as={TextInput} onChange={handleChange} value={value} />
<FormField.Hint>Add a valid email</FormField.Hint>
</FormField.Field>
</FormField>
);
};
Required
Set the isRequired prop of the wrapping 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} onChange={handleChange} value={value} />
</FormField.Field>
</FormField>
);
};
Icons
InputGroup is available to add icons to the TextInput. Internally, a container div element is
used with relative position styling on the div and absolute position styling on the start and end
icons. InputGroup.InnerStart and InputGroup.InnerEnd are used to position elements at the start
and end of the input. “start” and “end” are used instead of “left” and “right” to match
CSS Logical Properties
and will be semantically correct in left-to-right and right-to-left languages.
InputGroup.InnerStart and InputGroup.InnerEnd subcomponents can handle any child elements, but
are built for icons. The default width is 40px, which is perfect for icons. If you need to use
something else, be sure to set the width property of InputGroup.InnerStart or
InputGroup.InnerEnd to match the intended width of the element. Do not use the cs prop or any
method to change width. The width prop is used to correctly position other inner elements.
Do not use FormField.Input as={InputGroup} — that breaks label association. Render
FormField.Field as={InputGroup}, hoist the input id from the Form Field model, and set it on
InputGroup.Input (see the Icons example).
import React from 'react';
import {
FormField,
useFormFieldInput,
useFormFieldModel,
} from '@workday/canvas-kit-react/form-field';
import {SystemIcon} from '@workday/canvas-kit-react/icon';
import {InputGroup} from '@workday/canvas-kit-react/text-input';
import {mailIcon} 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 model = useFormFieldModel();
const {id: formFieldInputId} = useFormFieldInput(model);
return (
<FormField model={model}>
<FormField.Label>Email</FormField.Label>
<FormField.Field as={InputGroup}>
<InputGroup.InnerStart>
<SystemIcon icon={mailIcon} />
</InputGroup.InnerStart>
<InputGroup.Input id={formFieldInputId} autoComplete="email" />
<InputGroup.InnerEnd>
<InputGroup.ClearButton />
</InputGroup.InnerEnd>
</FormField.Field>
</FormField>
);
};
Accessibility Note: Canvas Kit icons are already hidden from assistive technology — their SVG markup sets
role="presentation"andfocusable="false"— so decorative icons like the mail icon in this example need no extra attributes. If an icon conveys meaning beyond the label text, provide that meaning as text for screen readers.
Error States
Form Field provides error and caution states for Text Input. Set the error prop on Form Field to
"error" or "caution" and use FormField.Hint to provide error messages. See
Form Field’s Error documentation for examples and
accessibility guidance.
Accessibility
The primary accessibility goal for TextInput is to give every user a visible, persistent label and
clear instructions, and to ensure assistive technology users can identify the single-line field and
hear hints, errors, required state, and input purpose when the control receives focus. Use
TextInput for single-line values (names, emails, short answers). For multiple lines or paragraphs
of text, use TextArea instead.
Minimum Accessible Structure
Build on the Basic example: label first, then the input inside FormField.Field. This order matches
the DOM reading sequence and ensures the label’s htmlFor targets the <input> before hint text
follows the control.
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>;Every TextInput requires FormField, a visible FormField.Label, and
FormField.Input as={TextInput} so the control has a programmatically determinable name,
relationships, and instructions. See
FormField’s accessibility documentation for shared
form-field guidance. Include FormField.Hint for instructions or validation
messages—FormField associates that text with the input through aria-describedby.
Built-in Behaviors
Canvas Kit applies these automatically when you compose TextInput with FormField subcomponents
(and InputGroup, when used). Do not duplicate them in consuming code.
ARIA and DOM (applied by subcomponents):
TextInput: Renders a native<input type="text">by default. Screen readers identify it as a single-line text input.TextInputdisabled: Maps to the nativedisabledattribute; disabled fields are removed from the tab order.InputGroup.ClearButton: Setsrole="presentation"andtabIndex={-1}so the control is not in the tab order and is not exposed as an operable button to screen readers. Clearing is available via native keyboard editing in the input.InputGroup.Input: Always ensures aplaceholderattribute exists (empty string when unset) so:placeholder-shownstyling for the clear button works correctly.- Canvas Kit icons (for example
SystemIconinsideInputGroup): SVG markup includesrole="presentation"andfocusable="false", which removes the impliedimgrole. Decorative icons need noaria-hidden.
Keyboard (standard TextInput behavior):
TextInput uses native <input> keyboard behavior (tab order, label activation, and text-editing
shortcuts). Do not add custom key handlers that prevent standard text editing.
InputGroup.ClearButton is intentionally not keyboard-focusable; users clear the value with
standard input editing keys.
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 current value or “blank” is announced when the input receives focus.
- The Caution state is visual only —
aria-invalidis not set forerror="caution". - Disabled inputs may be announced as unavailable and are skipped in the tab order.
InputGroup.ClearButtonis not announced as a separate operable control.- Icons rendered inside
InputGroup.InnerStartorInputGroup.InnerEndare not announced, because their SVG markup usesrole="presentation".
For rendered label, input, and hint association markup, see the DOM examples in
FormField’s Built-in Behaviors. TextInput
renders a native <input> (see FormField examples).
Accessibility Requirements
Required in application code for an accessible TextInput. Rows marked (conditional) apply only
when the situation matches—otherwise omit.
If no design spec is provided: use a visible FormField.Label, wrap the control with
FormField.Input as={TextInput}, omit isHidden, omit a custom id unless testing or composition
requires it, omit a ref unless programmatic focus is required, and omit InputGroup unless icons
or a clear control are part of the design.
Programmatic focus (conditional — omit by default):
Use a ref when the product needs to move focus to the input after an action (for example, focusing
the field after a validation error, or a control that focuses the input). Do not attach a ref or
call focus() unless the design or developer asks for it. See Ref Forwarding
under Usage for a complete Storybook example.
const Example = () => {
const ref = React.useRef<HTMLInputElement>(null);
const handleClick = () => {
ref.current?.focus();
};
return (
<>
<FormField>
<FormField.Label>Email</FormField.Label>
<FormField.Field>
<FormField.Input as={TextInput} ref={ref} />
</FormField.Field>
</FormField>
<PrimaryButton onClick={handleClick}>Focus Text Input</PrimaryButton>
</>
);
};InputGroup with icons (conditional):
When the design includes start/end icons or a clear control, compose InputGroup as
FormField.Field (not as FormField.Input) and wire the input id from the Form Field model:
import {
FormField,
useFormFieldInput,
useFormFieldModel,
} from '@workday/canvas-kit-react/form-field';
import {SystemIcon} from '@workday/canvas-kit-react/icon';
import {InputGroup} from '@workday/canvas-kit-react/text-input';
import {mailIcon} from '@workday/canvas-system-icons-web';
const model = useFormFieldModel();
const {id: formFieldInputId} = useFormFieldInput(model);
<FormField model={model}>
<FormField.Label>Email</FormField.Label>
<FormField.Field as={InputGroup}>
<InputGroup.InnerStart>
<SystemIcon icon={mailIcon} />
</InputGroup.InnerStart>
<InputGroup.Input id={formFieldInputId} autoComplete="email" />
<InputGroup.InnerEnd>
<InputGroup.ClearButton />
</InputGroup.InnerEnd>
</FormField.Field>
</FormField>;| Requirement | How to satisfy |
|---|---|
| Input wiring | FormField.Input as={TextInput} wrapping every TextInput instance. See FormField accessibility for label, hint, error, and required wiring |
| Autocomplete (conditional) | autoComplete on FormField.Input (or InputGroup.Input) with an appropriate token (e.g. "email", "name", "street-address", "tel"). See Identify Input Purpose |
Input type (conditional) | More specific type than "text" (e.g. "email", "tel", "url", "search") when a specialized mobile keyboard improves entry |
| Icons / clear control (conditional) | FormField.Field as={InputGroup} + InputGroup.Input with hoisted id (see InputGroup with icons above). Decorative icons need no extra attributes; when an icon conveys meaning beyond the label, convey that meaning as text |
| Programmatic focus (conditional) | ref on FormField.Input and call focus() when moving focus to the field after an action—omit by default (see Programmatic focus above) |
Summary for code generation:
- REQUIRED: visible label,
FormField.Input as={TextInput}wiring - CONDITIONAL:
autoComplete, specializedtype,InputGroupicon/clear composition with hoistedid, programmatic focus viaref. See FormField accessibility for shared FormField conditionals (hint/error, required, disabled, placeholder, stableid).
Anti-Patterns
Do not generate code that does the following (see Accessibility Requirements above for what to supply instead):
- Unlabeled text inputs: Do not use
TextInputwithoutFormFieldandFormField.Label(see Minimum accessible structure). For shared FormField anti-patterns (manual ARIA wiring, placeholder-only labels, color-only errors, broken ID references), see FormField Anti-Patterns. - Multi-line content in
TextInput: Do not useTextInputwhen the user needs to enter paragraphs or multi-line text; use TextArea instead. FormField.Input as={InputGroup}: Do not putInputGrouponFormField.Input— that breaks label association. UseFormField.Field as={InputGroup}, hoistidfromuseFormFieldInput(model), and pass it toInputGroup.Input(seeInputGroupwith icons in Accessibility Requirements and Icons under Usage).- Re-wiring
ClearButtona11y: Do not overrideInputGroup.ClearButton’sroleortabIndexto make it a focusable, announced button — Canvas Kit intentionally keeps clearing on the input. - Redundant
aria-hiddenon icons: Do not addaria-hiddento Canvas Kit icons — their SVG markup already setsrole="presentation"andfocusable="false". - Meaningful icons without a text alternative: Do not rely on an icon inside
InputGroupto convey information beyond the label; because icons are presentational, that meaning must come from text such asFormField.LabelorFormField.Hint. - Programmatic focus by default: Do not attach a
refor callfocus()on the input unless the design or developer asks for it (see Programmatic focus in Accessibility Requirements).
Component API
TextInput
Props
Props extend from input. Changing the as prop will change the element interface.
| Name | Type | Description | Default |
|---|---|---|---|
error | | The type of error associated with the TextInput (if applicable). | |
width | number | string | The width of the TextInput. | |
grow | boolean | True if the component should grow to its container's width. False otherwise. | |
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 |
TextInput.ErrorType
Basic type information:
ErrorTypeInputGroup
An InputGroup is a container around a with optional inner start and end
elements. The inner start and end elements are usually icons or icon buttons visually represented
inside the input. The InputGroup will add padding to the input so the icons/buttons display
correctly. This component uses React.Children.map and React.cloneElement from the
React.Children API. This means all children must be
InputGroup.* components. Any other direct children will cause issues. You can add different
elements/components inside the and
subcomponents.
<InputGroup>
<InputGroup.InnerStart as={SystemIcon} pointerEvents="none" icon={searchIcon} />
<InputGroup.Input />
<InputGroup.InnerEnd>
<TertiaryButton tabIndex={-1} icon={xIcon} size="small" />
</InputGroup.InnerEnd>
</InputGroup>
Layout Component
InputGroup 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 InputGroupModelConfig are ignored.
| Name | Type | Description | Default |
|---|---|---|---|
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. | 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. |
InputGroup.InnerStart
Basic type information:
InputGroupInnerStartInputGroup.Input
Basic type information:
InputGroupInputInputGroup.InnerEnd
Basic type information:
InputGroupInnerEndInputGroup.ClearButton
Basic type information:
ClearButtonModel
Content Guidelines
- Labels for Text Inputs are written in title case.