Text Input
Input with text type for single line of text, typically used within a form
import { TextInput } from "@vitality-ds/components";
Building a form? Checkout FormField
Text input allows users to enter and edit text. A typical use case is to get a single line of text within a form, for example a first name. If multiple lines of text were required, for example a review or description, a <textarea>
input would be more applicable. Typically a text input would render with a <label>
. In vitality, <label>
's are handled by the FormField component.
Below is an example of <TextInput />
with just the required props.
<TextInput name="nameOfInput" value={null} id="input_id" onChange={(event) => console.log(event.target.value)} />
name
and value
work together to assign a descriptive name to the input value. If asking for first name, a good example would be name="firstName"
. value
would be equal to what the user inputs as their first name or the value of stored data in the case of a prepopulated input. When used in the context of a form, a change-handling function will typically use [event.target.name]: event.target.value
to set data .
() => { const [formData, setFormData] = React.useState({}); function changeHandler(event) { setFormData({ ...formData, [event.target.name]: event.target.value, }); } return ( <Stack> <label htmlFor="firstName">First Name</label> <TextInput value={formData.firstName} name="firstName" id="first_name" onChange={changeHandler} /> <label htmlFor="lastName">Last Name</label> <TextInput value={formData.lastName} name="lastName" id="last_name" onChange={changeHandler} /> </Stack> ); };
This is an optional prop which align's the text within the input to either the left or the right of the input field.
<Stack> <label htmlFor="firstName">First Name</label> <TextInput id="firstName" textAlign="left" placeholder="Left Aligned" name="FirstName" onChange={(event) => console.log(event.target.value)} value={null} /> <label htmlFor="lastName">Last Name</label> <TextInput id="lastName" textAlign="right" placeholder="Right Aligned" name="LastName" onChange={(event) => console.log(event.target.value)} value={null} /> </Stack>
This is an optional prop which will display an example of what the input value could be. The colour of the placeholder will appear as a light grey colour and will disappear once any value
is passed. Placeholder Text has no interaction over the <TextInput />
's value
While placeholders can help, they can also hinder, you should heavily consider how placeholder text is used in the context of your form. Consider the <FormField />
component's helperMessage
rather than a placeholder for providing additional context.
If you are not using a Vitality FormField then you should create your own label. Add a
htmlFor
on the label with the same value as the ID on your TextInput.
- Short and simple placeholders that help describe use
- No more than 3 words
- Wordy placeholders
- Using placeholders as validation messages
- The use of a placeholder in lieu of a label
This example shows what it looks like without a value
and with a value
.
Edit the first <TextInput />
to see it in action.
() => { return ( <Stack> <label htmlFor="first_name">First Name</label> <TextInput placeholder="e.g. Joe" id="first_name" name="firstName" onChange={(event) => console.log(event.target.value)} value={null} /> <label htmlFor="last_name">Last Name</label> <TextInput placeholder="e.g. Bloggs" id="last_name" name="lastName" onChange={(event) => console.log(event.target.value)} value="Smith" /> </Stack> ); };
hasError
is an optional prop, which when passed into <TextInput />
will display a red border. Used to display validation failures.
Use in combination with the <FormField>
's validationMessage
prop for maximum effectiveness.
() => { return ( <Stack> <label htmlFor="name">Name</label> <TextInput hasError id="name" name="name" onChange={(event) => console.log(event.target.value)} value={null} /> </Stack> ); };
disabled
is an optional prop which when passed, disables the ability for the user to interact with the <TextInput />
.
<TextInput disabled />
will still display placeholders and manually inserted values, however the user will be incapable of interacting with them.
- Having placeholders on disabled inputs
() => { return ( <Stack> <label htmlFor="first_name">First Name</label> <TextInput disabled id="first_name" name="firstName" onChange={(event) => console.log(event.target.value)} value={null} /> <label htmlFor="middle_name">Middle Name</label> <Stack direction="horizontal"> <TextInput disabled placeholder="e.g. Jackson" id="middle_name" name="middleName" onChange={(event) => console.log(event.target.value)} value={null} /> <p>← This is an example of a placeholder on a disabled field</p> </Stack> <label htmlFor="family_name">Family Name</label> <Stack direction="horizontal"> <TextInput disabled id="family_name" name="familyName" onChange={(event) => console.log(event.target.value)} value="Smith" /> <p>← This is an example of a value on a disabled field</p> </Stack> </Stack> ); };
Text input adornments allow you to convey additional context or functionality within your text input. The TextInput
component has 4 different adornment types that can be applied to both the start and end of the text input via the adornStart
and adornEnd
props. Both receive an object with a type
and further props
.
Use this to add implied text that you don't want to be included in the data or a conversion from one text format to another for consistent data. Common use cases would include adding currency symbols and/or listing the currency code, or converting from the wanted input to more human-readable text, for example converting duration in minutes to hours and minutes.
The options you have for this adornment type are limited to text
, which should generally be short, concise and directly related to the type of data that the input requires.
<Stack align="stretch"> <label>Amount paid</label> <TextInput adornStart={{ type: "text", props: { text: "$" } }} adornEnd={{ type: "text", props: { text: "AUD" } }} onChange={(event) => console.log(event.target.value)} name="amountPaid" value={null} /> <label>Appointment duration (minutes)</label> <TextInput adornEnd={{ type: "text", props: { text: "1h 30m" } }} onChange={(event) => console.log(event.target.value)} name="amountPaid" value={90} /> </Stack>
Logic not included: Any sort of calculation or logic around the adornment's text should occur outside the component and simply be passed to it.
Icons help provide context and visual support to describe the type of input required. For example, using a check mark to show that validation was successful or a magnifying glass on a search input.
See here for more information on icons.
The options you have for this adornment type are limited to icon
and color
, which references the available colours on the Icon
component.
<Stack align="stretch"> <label>Appointment Id</label> <TextInput adornStart={{ type: "icon", props: { icon: Search } }} onChange={(event) => console.log(event.target.value)} name="amountPaid" value={null} /> <label>Medicare card number</label> <TextInput adornEnd={{ type: "icon", props: { icon: Completed, color: "success" } }} onChange={(event) => console.log(event.target.value)} name="amountPaid" value={"1234 12345 1"} /> <label>Credit card number</label> <TextInput adornEnd={{ type: "icon", props: { icon: Payment } }} onChange={(event) => console.log(event.target.value)} name="credit" size={24} placeholder="XXXX-XXXX-XXXX-XXXX" /> </Stack>
Whilst they should be used sparingly as adornments, buttons can provide useful, interim actions to aid in data input. For example, triggering a validation action on that input.
() => { const [isValid, setIsValid] = React.useState(); return ( <Stack align="stretch"> <label>Membership Number</label> <TextInput adornStart={ isValid && { type: "icon", props: { icon: Completed, color: "success" }, } } adornEnd={{ type: "button", props: { label: "Check Eligibility", onClick: () => setIsValid(true), }, }} onChange={(event) => console.log(event.target.value)} placeholder="eg. 123456" name="amountPaid" value={null} /> </Stack> ); };
Whilst they should be used sparingly as adornments, iconButtons can provide useful, interim actions to aid in data input. For example, showing/hiding a user's password.
- Always provide a tooltip as per the guidelines for IconButton
- Don't overuse iconButtons or put too much complex logic in their actions
- Try not to use iconButtons in conjunction with icon adornments
() => { const [show, setShow] = React.useState(false); const [name, setName] = React.useState("Marianne"); return ( <Stack align="stretch"> <label>Please enter your password</label> <TextInput adornEnd={{ type: "iconButton", props: { icon: <ReviewedOff />, tooltipContent: "Show/hide password", onClick: () => setShow(!show), }, }} onChange={(event) => console.log(event.target.value)} type={show ? "text" : "password"} name="amountPaid" value="password123!" /> <label>Please enter your name</label> <TextInput adornEnd={ name && { type: "iconButton", props: { icon: <Close />, tooltipContent: "Clear", onClick: () => setName(""), }, } } onChange={(event) => setName(event.target.value)} name="name" value={name} /> </Stack> ); };
Use Shortcut adornments to help the user to understand how to focus or activate a text input. A common shortcut may be ⌘ F
to activate a find/search input.
- If a text input can be activated via a keyboard shortcut, use this adornment to aid in discoverability.
- Choose keyboard shortcuts with careful consideration in your product
- Use symbols such as ⌘ and ⌥
- Try not to override native browser shortcuts unless you are certain it is the desired behaviour to support the user
<Stack align="stretch"> <TextInput adornEnd={{ type: "shortcut", props: { keys: ["⌘", "K"] }, }} placeholder="Find in page..." name="findInPage" value={null} /> </Stack>
Use spinner adornments to help the user to understand if the text input is processing data or awaiting an asynchronous function. For common search functionality, consider using a SearchInput
<Stack align="stretch"> <TextInput adornEnd={{ type: "spinner", }} placeholder="Loading..." name="Loading" value={null} /> </Stack>
TextInput accepts forwarded refs should you need to pass one, and ref.current
returns all TextInput
states, props and html data attributes. To more specifically.
() => { const inputRef = React.useRef(); React.useEffect(() => { console.log(inputRef); }, []); function handleChange() { function getRandomRGB() { return Math.floor(Math.random() * 255); } const rgb = `rgb(${getRandomRGB()}, ${getRandomRGB()}, ${getRandomRGB()})`; inputRef.current.style.background = rgb; } return ( <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}> <label htmlFor="name">Name</label> <TextInput ref={inputRef} name="name" id="name" onChange={() => handleChange()} /> </DocsFlex> ); };
Input masking can be a great way to alleviate user concerns over incorrectly entering their information, and at the same time ensure they enter their data in the required or preferred format. Input Masks can be a good solution for fields whose value is restricted to a specific format, length or shape. Examples include credit card number, times, dates or health fund numbers.
<TextInput placeholder="Type three numbers" maskProps={{ mask: ["(", /[1-9]/, /\d/, /\d/, ")"], guide: true }} />
The mask
prop takes an array that defines how the user input is going to be masked.
You can define a mask with an array. Each element in the array has to be either a string or a regular expression. Each string is a fixed character in the mask and each regular expression is a placeholder that accepts user input. The regular expression will be used to test user input and either allow it or reject it.
For example, a mask for an Australian phone number such as (02) 3292-4932
, could be:
() => { const mask = [ "(", /[0]/, /[1-9]/, ")", " ", /\d/, /\d/, /\d/, /\d/, "-", /\d/, /\d/, /\d/, /\d/, ]; return ( <TextInput placeholder="Type a phone number" maskProps={{ mask: mask, guide: true }} /> ); };
That means the user can enter only a 0 in the first placeholder, a number between 1 and 9 in the second placeholder, and only a digit in the placeholders after that. Any valid regular expression should work.
- When fields are empty, try to use placeholders that represent the mask so users know what to expect when the mask comes into effect.
- Use masks for format validation in conjunction with general form/input validation. Validation should take place _after_ the user exits the input field. For this, prefer `blur` or `focusout`
- Masks which do heavy, unnecessary re-formatting of the user's input
- Using masks on text inputs unless a specific use case.
You can generally configure a masked input in one of below ways.
- Show placeholder text when empty, display the mask's placeholders as the user types.
<TextInput placeholder="Type three numbers" maskProps={{ mask: ["(", /[1-9]/, /\d/, /\d/, ")"], guide: true }} />
- Show placeholder text when empty, don't display the mask's placeholders at all.
<TextInput placeholder="Type three numbers" maskProps={{ mask: ["(", /[1-9]/, /\d/, /\d/, ")"], guide: false, }} />
- No placeholder text, only display the mask's placeholders when the user types.
<TextInput maskProps={{ mask: ["(", /[1-9]/, /\d/, /\d/, ")"], guide: true, }} />
- No placeholder text, always show the mask's placeholders.
<TextInput maskProps={{ mask: ["(", /[1-9]/, /\d/, /\d/, ")"], showMask: true, }} />
The placeholder character represents the fillable spot in the mask. The default placeholder
character is underscore, _
. To maintain consistency, most masks should use the default.
You can pass a different placeholder character. For example, the unicode character U+2000
would
make the mask above look like ( ) -
. In JavaScript, you would pass such unicode character
as '\u2000'
.
Note: you cannot use a mask that has a placeholder character hard-coded in it. That is, since the default placeholder character is
_
, you cannot have a mask that looks like_111_
unless you passplaceholderChar
that is not_
and doesn't exist in your mask.
<FormField type="text" label="Enter your membership number" helperMessage="You can locate your membership number on the back of your membership card." inputProps={{ maskProps: { mask: [/[1-9]/, /\d/, /\d/, /\d/, " ", /\d/, /\d/], placeholderChar: "\u25E6", showMask: true, }, }} />
keepCharPositions
changes the general behavior of the way users input text. By default, adding characters causes existing characters to advance. And deleting characters
causes existing characters to move back. When set to true
, adding or deleting characters will not affect the positions of existing characters.
<Stack> <TextInput maskProps={{ mask: ["(", /[1-9]/, /\d/, /\d/, ")"], showMask: true, }} /> <TextInput maskProps={{ mask: ["(", /[1-9]/, /\d/, /\d/, ")"], keepCharPositions: true, showMask: true, }} /> </Stack>
showMask
is a boolean that tells the Text Mask component to display the mask as a
placeholder in place of the regular placeholder when the input element value is empty.
() => { const [value, setValue] = React.useState(); return ( <Stack align="stretch"> <TextInput onChange={(event) => setValue(event.target.value)} name="inputName" value={value} maskProps={{ guide: true, showMask: true, mask: [ "(", /[1-9]/, /\d/, /\d/, ")", " ", /\d/, /\d/, /\d/, "-", /\d/, /\d/, /\d/, /\d/, ], }} /> Value: {value} </Stack> ); };
Vitality provides a selection of pre-set formats and configurations for common input masks. Shown below, these can be used by passing the presetMask
prop to maskProps
.
() => { return ( <Stack> Medicare Number <TextInput maskProps={{ presetMask: "medicare", }} /> Australian Post Code <TextInput size={4} maskProps={{ presetMask: "ausPostCode", }} /> Phone Number <TextInput maskProps={{ presetMask: "ausPhone", }} /> Credit Card <TextInput value={null} maskProps={{ presetMask: "creditCard", }} /> Date <TextInput value={null} maskProps={{ presetMask: "date", }} /> Duration (minutes) <TextInput value={null} maskProps={{ presetMask: "durationMinutes", }} /> Bank BSB <TextInput value={null} maskProps={{ presetMask: "bankBsb", }} /> </Stack> ); };
The text input will take any valid <input type="text">
attributes. See the MDN web docs for more information. See maxLength
example below.
<Stack align="stretch"> <TextInput maxLength="4" size={21} onChange={(event) => console.log(event.target.value)} name="inputName" value={null} /> <Typography appearance="caption">Maximum 4 characters allowed</Typography> </Stack>
Inputs of type="number"
should be avoided as they have potential usability issues:
- Allowing certain non-numeric characters ('e', '+', '-', '.') and silently discarding others
- Users are not given feedback on what type of characters
<input type="number">
accepts, and assistive technologies don't alert the user that their input has been silently discarded. - The functionality of scrolling to increment/decrement the number can cause accidental and hard-to-notice changes and more - see this article by the GOV.UK Design System team for a more detailed explanation.
For number validation, a recommended alternative is to use the default TextInput
(where type="text"
) with the maskProps
and inputmode
attributes, for example:
-
The
maskProps
prop has the capability to apply a regex mask, such as/\d/
, which permits any digit to be entered. Multiple instances of/\d/
can be included in the mask array as necessary for the specific data. For this exmaple, entering a birth year, restricting the input to 4 digits is recommended hiding the guide is also recommended as showing the guides will make users feel as though they must fill all available digits. -
By using
inputMode="numeric"
, mobile compatibility is enhanced by replacing the default keyboard with a numeric keyboard, resulting in improved accessibility and data accuracy.
<Stack> <Typography>Birth year</Typography> <TextInput maskProps={{ guide: false, mask: [/\d/, /\d/, /\d/, /\d/], }} inputMode="numeric" placeholder="e.g. 2003" size={30} /> </Stack>
- Use `type='text'` and set `inputMode='numeric'` to utilise the numeric keyboard on mobile devices
- Do not use `<input type='number'>` unless your user research shows that there is a need for it.
If an input of
type="number"
is really needed, you can simply pass that prop to the TextInput component:
<TextInput type="number" min={0} max={10} />
Read more about the number
input type on MDN docs.
Description
Content to "adorn" the end of the input - typically text, icons or buttons.
Type
{ type: "text"; props: TextAdornmentProps; } | { type: "icon"; props: IconAdornmentProps; } | { type: "iconButton"; props: IconButtonProps; } | { type: "button"; props: ButtonAdornmentProps; } | { type: "shortcut"; props: ShortcutAdornmentProps; } | { type: "spinner"; props?: never; }
Description
Content to "adorn" the start of the input - typically text, icons or buttons.
Type
{ type: "text"; props: TextAdornmentProps; } | { type: "icon"; props: IconAdornmentProps; } | { type: "iconButton"; props: IconButtonProps; } | { type: "button"; props: ButtonAdornmentProps; } | { type: "shortcut"; props: ShortcutAdornmentProps; } | { type: "spinner"; props?: never; }
Type
never
Description
Determines whether the user has the ability to interact with the input.
Type
boolean
Description
Adds additional styling to indicate failed validation.
Type
boolean
Default Value
false
Description
The id of the input. Used to connect label to input.
Type
string
Description
Mask Props
Type
MaskProps
Description
The name of the text input. Submitted with its owning form as part of a name/value pair.
Type
string
Description
The callback function. Triggered when any character is added or removed from the input.
Type
ChangeEventHandler<HTMLInputElement> & ((e: ChangeEvent<HTMLInputElement>) => void)
Description
Shows an example of what could be inputted. Only visible when no value is present. Commonly used as a "pseudo" helper message.
Type
string
Type
never
Description
aligns the Text of the input
Type
("left" | "right"
Description
A string specifying the type of input element type to render. If omitted (or an unknown value is specified), the input type text is used, creating a plaintext input field.
Type
HTMLInputTypeAttribute
Default Value
text
Description
The inputted value. Can be used to prepopulate data.
Type
(string | number | readonly string[]) & string
Type
"start" | "end"
Type
string
Type
"primary" | "accent" | "success" | "info" | "warning" | "critical" | "inherit" | "hiContrast" | "disabled" | "moderate" | "lowContrast"
Type
FC<IconType>
Description
The visual appearance of the component.
Type
"primary" | "warning" | "link" | "onSurface" | "ghost" | "standard" | "destructive"
Description
Text contents of the component.
Type
ReactNode
Description
The Icon to the left of the Button text.
Type
ReactNode
Description
The Icon to the right of the Button text.
Type
ReactNode
Description
Show a spinner as an overlay on the button when an asynchronous process is loading. Disables the button whilst true
Type
(boolean | "true"
Type
string
Type
Ref<HTMLButtonElement>
Type
boolean
Description
The size of the button
Type
"default" | "compact"
Description
IconButtons do not accept children. Use the icon prop to pass the icon.
Type
never
Description
Optional href for cases where clicking the button should navigate to another page. Component will render an <a> element instead of a button.
Type
string
Description
Pass in an appropriate Vitality icon that describes the action to be performed.
Type
IconType
Description
For toggle states. State should be handled outside the component and passed in.
Type
boolean
Description
Action to perform when clicking the IconButton
Type
MouseEventHandler<HTMLButtonElement> & MouseEventHandler<HTMLAnchorElement> & ((event: unknown) => unknown)
Type
Ref<HTMLButtonElement> & Ref<HTMLAnchorElement>
Type
"compact"
Description
Support the user to understand what action will be performed
Type
string
Type
string[]