Props
Usage guidelines
- Any time succinct data needs to be entered by a user, like a date, email address, name, or Pin title.
- Situations where long amounts of text need to be entered, since the full content of the TextField will be truncated. Use TextArea instead.
Best practices
Use helper text for important information. Helper text helps users understand how to complete the text field or to indicate any needed input.
Put essential information in the placeholder text, since it disappears when the user types. The placeholder text is not a replacement for the label.
Always ensure the text field has a visible label. The label provides context and supports users when filling in information.
Remove the label, as this creates accessibility and usability issues.
Only place related fields on the same line.
Place unrelated text fields on the same line, as this can create comprehension issues.
Provide clear and useful error messages that help the user fix the issue. Error messages should be displayed in a timely manner — typically once the field loses focus or when the form is submitted.
Display generic error messages, such as "There is an error".
Consider all text fields as required, unless explicitly noted as optional.
Mark fields as required.
Accessibility
Comprehension
Be sure to provide instructions to help users understand how to complete the form and use individual form controls.
Labels
TextField comes with
Label built-in: just use the label
prop. We strongly encourage always supplying a label. Be sure to provide a unique id
so the Label is associated with the correct TextField.
If TextField is labeled by content elsewhere on the page, there are different approaches to make the field accessible. See the Label section for more information
Validation
When providing a validation message, make sure the instructions are clear and help users complete the field. For example, "Passwords must contain at least 20 characters". In addition, use the helper text to provide instructions to help users understand how to complete the text field or to indicate any needed input, allowed formats, timing limitations, or other pertinent information.
These practices give users of assistive technologies more information about the form, helping them to fill it out.
TextField has conventional keyboard support.
- Users relying on the keyboard expect to move focus to each TextField by using the tab key or shift+tab when moving backwards.
- Setting
disabled
will prevent TextField from receiving keyboard focus or input.
Autofocus
TextField intentionally lacks support for autofocus. Generally speaking, autofocus interrupts normal page flow for screen readers making it an anti-pattern for accessibility.
onSubmit
TextField is commonly used as an input in forms alongside submit buttons. In these cases, users expect that pressing Enter or Return with the input focused will submit the form.
Out of the box, TextField doesn't expose an onSubmit
handler or individual key event handlers due to the complexities of handling these properly. Instead, developers are encouraged to wrap TextField in a <form>
with an onSubmit
handler..
Localization
Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.
import { useState } from 'react'; import { Box, DefaultLabelProvider, TextField } from 'gestalt'; export default function Example() { const [password, setPassword] = useState(''); return ( <DefaultLabelProvider labels={{ TextField: { accessibilityHidePasswordLabel: 'Passwort verstecken', accessibilityShowPasswordLabel: 'Passwort anzeigen', }, }} > <Box padding={8} width="100%"> <TextField id="enter-password" label="Konto-Passwort" onChange={({ value }) => setPassword(value)} placeholder="Kennwort" type="password" value={password} /> </Box> </DefaultLabelProvider> ); }
Variants
Size
TextField can have different sizes. The default size is md
(40px). The lg
size is 48px. For a dense variant, use the sm
(32px) variant.
import { useState } from 'react'; import { Box, Flex, TextField } from 'gestalt'; export default function TextFieldSizes() { const [input1text, setInput1Text] = useState(''); const [input2text, setInput2Text] = useState(''); const [input3text, setInput3Text] = useState(''); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <TextField helperText="Helper text" id="xs" label="Label" onChange={({ value }) => { setInput1Text(value); }} placeholder="Placeholder" size="sm" type="text" value={input1text} /> <TextField helperText="Helper text" id="sm" label="Label" onChange={({ value }) => { setInput2Text(value); }} placeholder="Placeholder" size="md" type="text" value={input2text} /> <TextField helperText="Helper text" id="lg" label="Label" onChange={({ value }) => { setInput3Text(value); }} placeholder="Placeholder" size="lg" type="text" value={input3text} /> </Flex> </Box> ); }
State
Enabled
The enabled state of Textfield that represents it can be interacted with.Error
TextField can display an error message. Simply pass in anerrorMessage
when there is an error present and TextField will handle the rest. Don't useerrorMessage
to provide feedback on character count errors. See the maximum length variant for more details.Read-only
Read-only TextFields are used to present information to the user without allowing them to edit the content. Typically they are used to show content or information that the user does not have permission or access to edit.Disabled
TextFields cannot be interacted with using the mouse or keyboard. They also do not need to meet contrast requirements, so do not use them to present info to the user (use "readOnly" instead).
import { useState } from 'react'; import { Box, Flex, TextField } from 'gestalt'; export default function Example() { const [valueSm, setValueSm] = useState('****maz@pinterest.com'); const [valueMd, setValueMd] = useState('****maz@pinterest.com'); const [valueLg, setValueLg] = useState('****maz@pinterest.com'); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <TextField id="variants-defaul-sm" label="Email address" onChange={({ value }) => setValueSm(value)} placeholder="Email" size="sm" value={valueSm} /> <TextField id="variants-default-md" label="Email address" onChange={({ value }) => setValueMd(value)} placeholder="Email" size="md" value={valueMd} /> <TextField id="variants-default-lg" label="Email address" onChange={({ value }) => setValueLg(value)} placeholder="Email" size="lg" value={valueLg} /> </Flex> </Box> ); }
import { useState } from 'react'; import { Box, Flex, TextField } from 'gestalt'; export default function Example() { const [valueSm, setValueSm] = useState('****maz@pinterest.com'); const [valueMd, setValueMd] = useState('****maz@pinterest.com'); const [valueLg, setValueLg] = useState('****maz@pinterest.com'); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <TextField disabled id="variants-disabled-sm" label="Email address" onChange={({ value }) => setValueSm(value)} placeholder="Name" size="sm" value={valueSm} /> <TextField disabled id="variants-disabled-md" label="Email address" onChange={({ value }) => setValueMd(value)} placeholder="Name" size="md" value={valueMd} /> <TextField disabled id="variants-disabled-lg" label="Email address" onChange={({ value }) => setValueLg(value)} placeholder="Name" size="lg" value={valueLg} /> </Flex> </Box> ); }
import { Box, Flex, TextField } from 'gestalt'; export default function Example() { return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <TextField errorMessage={"This field can't be blank!"} id="error-example-sm" label="Email address" onChange={() => {}} placeholder="Email" size="sm" value="" /> <TextField errorMessage={"This field can't be blank!"} id="error-example-md" label="Email address" onChange={() => {}} placeholder="Email" size="md" value="" /> <TextField errorMessage={"This field can't be blank!"} id="error-example-lg" label="Email address" onChange={() => {}} placeholder="Email" size="lg" value="" /> </Flex> </Box> ); }
import { useState } from 'react'; import { Box, Flex, TextField } from 'gestalt'; export default function Example() { const [valueSm, setValueSm] = useState('****maz@pinterest.com'); const [valueMd, setValueMd] = useState('****maz@pinterest.com'); const [valueLg, setValueLg] = useState('****maz@pinterest.com'); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <TextField id="variants-readonly-sm" label="Email address" onChange={({ value }) => setValueSm(value)} placeholder="Email" readOnly size="sm" value={valueSm} /> <TextField id="variants-readonly-md" label="Email address" onChange={({ value }) => setValueMd(value)} placeholder="Email" readOnly size="md" value={valueMd} /> <TextField id="variants-readonly-lg" label="Email address" onChange={({ value }) => setValueLg(value)} placeholder="Email" readOnly size="lg" value={valueLg} /> </Flex> </Box> ); }
Label
'label' is an optional prop; however, TextField should always be properly labelled. Learn about accessibility best practices regarding labels.
- Built-in label. Preferred. Consistent Textfield design and tested accessibility.
In some cases, the label for a TextField is represented in a different way visually, as demonstrated below. We can take 2 approaches in this case.
Labelled Textfield (Label + Textfield). This is the best approach when a custom label is needed. The label focuses the Textfield when pressed.
Hidden built-in label (Label + Textfield). This is the best approach when there's significant visual distance between the label and the input. You can set
labelDisplay="hidden"
to ensure TextField is properly labeled for screen readers while using a different element to represent the label visually. The 'visual' label doesn't focus the Textfield when pressed.
import { Box, Flex, TextField } from 'gestalt'; export default function Example() { return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <TextField id="textfieldexamplevisibleLabel" label="First name" labelDisplay="visible" onChange={() => {}} size="sm" /> <TextField id="textfieldexamplevisibleLabel" label="First name" labelDisplay="visible" onChange={() => {}} size="md" /> <TextField id="textfieldexamplevisibleLabel" label="First name" labelDisplay="visible" onChange={() => {}} size="lg" /> </Flex> </Box> ); }
import { Box, Flex, Label, Text, TextField } from 'gestalt'; export default function Example() { return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <Flex direction="column" gap={{ column: 2, row: 0 }}> <Label htmlFor="textfieldexampleHiddenLabel"> <Text size="300" weight="bold"> First name </Text> </Label> <TextField id="textfieldexampleHiddenLabel" labelDisplay="hidden" onChange={() => {}} size="sm" /> </Flex> <Flex direction="column" gap={{ column: 2, row: 0 }}> <Label htmlFor="textfieldexampleHiddenLabel"> <Text size="300" weight="bold"> First name </Text> </Label> <TextField id="textfieldexampleHiddenLabel" labelDisplay="hidden" onChange={() => {}} size="md" /> </Flex> <Flex direction="column" gap={{ column: 2, row: 0 }}> <Label htmlFor="textfieldexampleHiddenLabel"> <Text size="300" weight="bold"> First name </Text> </Label> <TextField id="textfieldexampleHiddenLabel" labelDisplay="hidden" onChange={() => {}} size="lg" /> </Flex> </Flex> </Box> ); }
import { Box, Flex, Text, TextField } from 'gestalt'; export default function Example() { return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <Flex direction="column" gap={{ column: 2, row: 0 }}> <Text size="300" weight="bold"> First name </Text> <TextField id="textfieldexampleHiddenLabel" label="First name" labelDisplay="hidden" onChange={() => {}} size="sm" /> </Flex> <Flex direction="column" gap={{ column: 2, row: 0 }}> <Text size="300" weight="bold"> First name </Text> <TextField id="textfieldexampleHiddenLabel" label="First name" labelDisplay="hidden" onChange={() => {}} size="md" /> </Flex> <Flex direction="column" gap={{ column: 2, row: 0 }}> <Text size="300" weight="bold"> First name </Text> <TextField id="textfieldexampleHiddenLabel" label="First name" labelDisplay="hidden" onChange={() => {}} size="lg" /> </Flex> </Flex> </Box> ); }
Helper text
Whenever you want to provide more information about a form field, you should use helperText
.
import { useState } from 'react'; import { Box, TextField } from 'gestalt'; export default function Example() { const [password, setPassword] = useState(''); return ( <Box padding={8} width="100%"> <TextField autoComplete="new-password" helperText="Password should be at least 20 characters long" id="variants-helper-text" label="Password" onChange={({ value }) => setPassword(value)} type="password" value={password} /> </Box> ); }
Maximum length
Textfield supports the native
maxlength input attribute. maxLength
sets the maximum number of characters allowed to be entered by the user in Textfield. maxLength
must be an integer value 0 or higher.
The user cannot exceed the maximum number of characters interacting with the component. Whenever possible, avoid setting initial values from the parent component's state that already exceed the maxLength
.
When maxLength
is passed to TextField, the component displays a character counter as well as a
warning or problem Status when the user reaches or the prepopulated controlled value exceeds the maximum length of characters.
The first example shows an empty Textfield with maxLength
set to 20 characters. The second example shows the warning and problem Status.
import { useState } from 'react'; import { Box, Flex, TextField } from 'gestalt'; export default function TextFieldExample() { const [value, setValue] = useState(''); const characterCount = 20; return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <TextField helperText="Enter a title that captures the imagination of Pinners" id="maxLength" label="Title" maxLength={{ characterCount, errorAccessibilityLabel: 'Limit reached. You can only use 20 characters in this field.', }} onBlur={() => {}} onChange={(e) => setValue(e.value)} onFocus={() => {}} placeholder="Enter your pin title" value={value} /> <TextField disabled helperText="Enter a title that captures the imagination of Pinners" id="maxLength" label="Title" maxLength={{ characterCount, errorAccessibilityLabel: 'Limit reached. You can only use 20 characters in this field.', }} onBlur={() => {}} onChange={(e) => setValue(e.value)} onFocus={() => {}} placeholder="Enter your pin title" value={value} /> </Flex> </Box> ); }
import { useState } from 'react'; import { Box, Flex, TextField } from 'gestalt'; export default function TextFieldExample() { const [valueA, setValueA] = useState('Delicious vegan soup'); const [valueB, setValueB] = useState('Delicious vegan noodle soup'); const characterCount = 20; const errorAccessibilityLabel = 'Limit reached. You can only use 20 characters in this field.'; return ( <Box padding={8} width="100%"> <Flex direction="column" gap={3}> <TextField helperText="Enter a title that captures the imagination of Pinners" id="maxLengthReached" label="Title" maxLength={{ characterCount, errorAccessibilityLabel }} onBlur={() => {}} onChange={({ value }) => setValueA(value)} onFocus={() => {}} placeholder="Enter your pin title" value={valueA} /> <TextField helperText="Enter a title that captures the imagination of Pinners" id="maxLengthExceeded" label="Title" maxLength={{ characterCount, errorAccessibilityLabel }} onBlur={() => {}} onChange={({ value }) => setValueB(value)} onFocus={() => {}} placeholder="Enter your pin title" value={valueB} /> <TextField disabled helperText="Enter a title that captures the imagination of Pinners" id="maxLengthReached" label="Title" maxLength={{ characterCount, errorAccessibilityLabel }} onBlur={() => {}} onChange={({ value }) => setValueA(value)} onFocus={() => {}} placeholder="Enter your pin title" value={valueA} /> <TextField disabled helperText="Enter a title that captures the imagination of Pinners" id="maxLengthExceeded" label="Title" maxLength={{ characterCount, errorAccessibilityLabel }} onBlur={() => {}} onChange={({ value }) => setValueB(value)} onFocus={() => {}} placeholder="Enter your pin title" value={valueB} /> </Flex> </Box> ); }
Password
TextField with type="password"
shows obfuscated characters by default. An icon button at the end of the field allows the user to toggle password visibility. This creates a more accessible experience by allowing the user to confirm what they have entered before submitting the form.
import { useState } from 'react'; import { Box, Flex, TextField } from 'gestalt'; export default function Example() { const [password, setPassword] = useState(''); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={6} width="100%"> <TextField id="enter-password" label="Account password" onChange={({ value }) => setPassword(value)} placeholder="6-18 characters" size="sm" type="password" value={password} /> <TextField id="enter-password" label="Account password" onChange={({ value }) => setPassword(value)} placeholder="6-18 characters" size="md" type="password" value={password} /> <TextField id="enter-password" label="Account password" onChange={({ value }) => setPassword(value)} placeholder="6-18 characters" size="lg" type="password" value={password} /> </Flex> </Box> ); }
Tags
You can include
Tag elements in the input using the tags
prop.
Note that TextField does not internally manage tags. Tag management should be handled in the application state through the component's event callbacks. We recommend creating new tags on enter key presses, and removing them on backspaces when the cursor is in the beginning of the field. We also recommend filtering out empty tags.
This example showcases the recommended behavior. In addition, it creates new tags by splitting the input on spaces, commas, and semicolons.
import { useRef, useState } from 'react'; import { Box, Flex, Tag, TextField } from 'gestalt'; export default function Example() { const [value, setValue] = useState(''); const [tags, setTags] = useState(['a@pinterest.com', 'b@pinterest.com']); const ref = useRef(null); const onChangeTagManagement = (e) => { // Create new tags around spaces, commas, and semicolons. const tagInput = e.value.split(/[\\s,;]+/); if (tagInput.length > 1) { setTags([ ...tags, // Avoid creating a tag on content after the separators, and filter out // empty tags ...tagInput.splice(0, tagInput.length - 1).filter((val) => val !== ''), ]); } setValue(tagInput[tagInput.length - 1]); }; const onKeyDownTagManagement = ({ event: { keyCode, currentTarget: { selectionEnd }, }, }) => { if (keyCode === 8 /* Backspace */ && selectionEnd === 0) { // Remove tag on backspace if the cursor is at the beginning of the field setTags([...tags.slice(0, -1)]); } else if (keyCode === 13 /* Enter */ && value.trim() !== '') { // Create a new tag on enter setTags([...tags, value.trim()]); setValue(''); } }; const renderedTags = tags.map((tag, idx) => ( <Tag key={tag} accessibilityRemoveIconLabel={`Remove ${tag} tag`} onRemove={() => { const newTags = [...tags]; newTags.splice(idx, 1); setTags([...newTags]); if (ref.current) { ref.current.focus(); } }} text={tag} /> )); return ( <Box padding={8} width="100%"> <Flex direction="column" gap={4}> <TextField ref={ref} autoComplete="off" helperText="Select your target locations" id="variants-tag-sm" label="Emails" onChange={onChangeTagManagement} onKeyDown={onKeyDownTagManagement} size="sm" tags={renderedTags} value={value} /> <TextField ref={ref} autoComplete="off" helperText="Select your target locations" id="variants-tags-md" label="Emails" onChange={onChangeTagManagement} onKeyDown={onKeyDownTagManagement} size="md" tags={renderedTags} value={value} /> <TextField ref={ref} autoComplete="off" helperText="Select your target locations" id="variants-tags-lg" label="Emails" onChange={onChangeTagManagement} onKeyDown={onKeyDownTagManagement} size="lg" tags={renderedTags} value={value} /> <TextField ref={ref} autoComplete="off" disabled helperText="Select your target locations" id="variants-tag-sm" label="Emails" onChange={onChangeTagManagement} onKeyDown={onKeyDownTagManagement} size="sm" tags={renderedTags} value={value} /> <TextField ref={ref} autoComplete="off" helperText="Select your target locations" id="variants-tag-sm" label="Emails" onChange={onChangeTagManagement} onKeyDown={onKeyDownTagManagement} readOnly size="sm" tags={renderedTags} value={value} /> <TextField ref={ref} autoComplete="off" errorMessage="Select minimum of five" helperText="Select your target locations" id="variants-tag-sm" label="Emails" onChange={onChangeTagManagement} onKeyDown={onKeyDownTagManagement} size="sm" tags={renderedTags} value={value} /> </Flex> </Box> ); }
Mobile
TextField supports some props to improve the mobile experience. Browsers display virtual keyboard when the user interacts with TextField. enterKeyHint
and inputMode
allow customizing the virtual keyboard for a better data input.
The mobileEnterKeyHint
prop presents to the user a more accurate action label for the enter key on virtual keyboards. These are the values for each use case:
- "enter": inserting a new line
- "done": there is nothing more to input and the input editor will be closed
- "go": taking the user to the target of the text they typed
- "next": taking the user to the next field that will accept text
- "previous": taking the user to the previous field that will accept text
- "search": taking the user to the results of searching for the text they have typed
- "send": delivering the text to its target
The mobileInputMode
prop presents to the user a more accurate action label for the enter key on virtual keyboards. These are the values for each use case:
- "none": No virtual keyboard. For when the page implements its own keyboard input control, for example DatePicker displays the calendar picker.
- "text": Standard input keyboard for the user's current locale.
- "decimal": Fractional numeric input keyboard containing the digits and decimal separator for the user's locale (typically "." or ",").
- "numeric": Numeric input keyboard, but only requires the digits 0–9.
Use type
when TextField needs to capture phone numbers, emails or URLs.
import { Box, Flex, Image, TextField } from 'gestalt'; export default function Example() { return ( <Box padding={8} width="100%"> <Flex direction="column" gap={2}> <TextField id="enterKeyHint" label="Text virtual keyboard with 'next'" mobileEnterKeyHint="next" onBlur={() => {}} onChange={() => {}} onFocus={() => {}} /> <Box height={100} width={200}> <Image alt="Image of a screenshot of a virtual keyboard on a mobile screen showing a text virtual keyboard with 'next'" naturalHeight={1} naturalWidth={1} src="https://i.ibb.co/qdMLb8t/IMG-2518.jpg" /> </Box> </Flex> </Box> ); }
import { Box, Flex, Image, TextField } from 'gestalt'; export default function Example() { return ( <Box padding={8} width="100%"> <Flex direction="column" gap={2}> <TextField id="decimal" label="Decimal virtual keyboard" mobileInputMode="decimal" onBlur={() => {}} onChange={() => {}} onFocus={() => {}} /> <Box height={100} width={200}> <Image alt="Image of a screenshot of a virtual keyboard on a mobile screen showing a decimal virtual keyboard" naturalHeight={1} naturalWidth={1} src="https://i.ibb.co/WxYtCdx/IMG-2520.jpg" /> </Box> </Flex> </Box> ); }
import { Box, Flex, Image, TextField } from 'gestalt'; export default function Example() { return ( <Box padding={8} width="100%"> <Flex direction="column" gap={2}> <TextField id="none" label="Numeric virtual keyboard" mobileInputMode="numeric" onBlur={() => {}} onChange={() => {}} onFocus={() => {}} type="date" /> <Box height={100} width={200}> <Image alt="Image of a screenshot of a virtual keyboard on a mobile screen showing a numeric virtual keyboard" naturalHeight={1} naturalWidth={1} src="https://i.ibb.co/tpZ9pV8/IMG-2519.jpg" /> </Box> </Flex> </Box> ); }
import { Box } from 'gestalt'; import { DatePicker } from 'gestalt-datepicker'; export default function Example() { return ( <Box padding={8} width="100%"> <DatePicker id="datepicker" label="No virtual keyboard" onChange={() => {}} /> </Box> ); }
Component quality checklist
Quality item | Status | Status description |
---|---|---|
Figma Library | Ready | Component is available in Figma for web and mobile web. |
Responsive Web | Ready | Component responds to changing viewport sizes in web and mobile web. |
Related
TextArea
When users need to enter long amounts of text, use TextArea to ensure the full content will be shown.
NumberField
For numerical input, use NumberField. Exceptions: for telephone numbers, use <TextField type="tel" />
. And for numerical input with possible leading 0's (e.g. ZIP codes), use <TextField type="text" />
.
SearchField
If the input is used for searching content, use SearchField.