Input and field
StableInput is the single-line text field, for names, prices, SKUs and anything else typed on one line. Field gives a control its label, a hint and an error, and wires them together for screen readers.
Playground
Change the props, then click into the field to see the teal focus edge and its halo.
<Input
aria-label="Product title"
placeholder="e.g. Arden ergonomic chair"
/>With a field
Put one control inside a Field to give it a label above and a hint below. The label points at the control, so clicking it focuses the field, and the hint becomes the control's description. Every field needs a label; an Input on its own needs an aria-label.
Shown on the product page and in search results.
| Part | What it does |
|---|---|
| Label | 13px at 550, 7px above the control. Always visible, in sentence case. |
| Control | An Input, a SearchField, or your own control reading useFieldControl. |
| Hint | 12.5px in quiet slate: what to enter, or how it is used. Hidden while there is an error. |
| Error | 12.5px in red with an icon: what is wrong and how to fix it. Replaces the hint and marks the control invalid. |
Sizes
sm 30px, md 36px (default) and lg 44px: the same heights as buttons, so a field and its button share a line. Text is 13, 14 and 15px; icons are 15, 16 and 18px.
Icon, prefix and suffix
icon leads, muted until the field has focus. prefix is text set tight against the value, so a price reads £18.00, never £ 18.00. suffix trails: a unit, a status or a small button. Clicking the icon, the prefix or the padding focuses the input; a button in the suffix keeps its own click.
States
Hover firms the hairline. Focus turns it teal and grows a soft 3px halo: click into any field to see it. An error swaps the teal for red. Read-only and disabled fields sink into the well.
Enter an email address in the correct format, like name@example.com
Set when the order was placed
Turn on Put on sale to set a sale price
Read-only or disabled?
| Behaviour | Read-only | Disabled |
|---|---|---|
| Focus and copy | Yes: it can be tabbed to, selected and copied. | No: Tab skips it. |
| Sent with the form | Yes | No |
| Use it for | Values people can see but not change here: an order number, a store address. | A field that is off because of another setting. Say which one nearby. |
Hints and errors
A hint says what to enter or how it will be used. An error says what is wrong and how to fix it: it replaces the hint, turns the edge red, sets aria-invalid on the control and settles 3px down into place.
Check when the form is sent, not on every keystroke, and clear the error as soon as the person starts fixing it. Press Send invite to try it.
Required and optional
Field has no required prop and draws no asterisk. Pass required to the control: it reaches the native input, so screen readers announce the field as required. Most fields in a form should be required, so mark the few that are not by ending their label with "(optional)".
Letters, numbers and dashes
GTIN, EAN or UPC
In a form
Pricing and inventory for a product. Fields sit two to a row on wide screens and stack on phones. Press Save product while the SKU is empty: every problem shows at once and focus moves to the first one, so a screen reader reads its error straight away.
Custom controls
Field works with any single control. Input and SearchField pick up its wiring on their own; for anything else, call useFieldControl() inside the control and put the id, description and invalid state on the element that takes focus.
For a real multi-line field use Textarea, which does this for you. Here a plain textarea, styled for this page, shows the wiring by hand. Type past 160 characters to see the error arrive.
121 of 160 characters. Shown under the page title in search results.
| useFieldControl() returns | Type | Put it on |
|---|---|---|
id | string | The focusable element, so the label points at it. |
labelId | string | Its aria-labelledby, when a label element cannot name it (a div with a role). |
describedBy | string | undefined | Its aria-describedby: the id of the hint or the error. |
invalid | boolean | Its aria-invalid, and your own error styling. |
In both themes
A filled field with a prefix, an error and a disabled field, light and dark side by side.
Includes VAT at 20%
Use letters, numbers and dashes only
Turn on Put on sale to set a sale price
Includes VAT at 20%
Use letters, numbers and dashes only
Turn on Put on sale to set a sale price
Guidelines
Enter a price in pounds and pence, like 18.00
Invalid input
- Size the field to the answer: a postcode field is short, a product title long.
- Units go in
prefixorsuffix, not the label: "Price" with £, not "Price (£)". - For money and weights use
inputMode="decimal"rather thantype="number": phones show a number pad, without the spinner arrows or scroll-wheel surprises. - Labels are nouns in sentence case. Hints are one short sentence, and never repeat the label.
Accessibility
- Field renders a real
labelpointing at the control's id (generated when you do not pass one), so screen readers read it as the name and clicking it focuses the control. - The hint or error is linked with
aria-describedby, alongside any of your own. An error also setsaria-invalid. The error's icon is hidden; the words carry the message. - Errors are not announced as they appear. After a failed submit, move focus to the first field with a problem, and it is read with its error.
- Without a Field, give the Input an
aria-labeloraria-labelledby. - Focus shows as a teal edge with a soft halo round the whole frame, not an outline on the text.
- Numerals in the field are tabular, so amounts line up as they are typed.
- Under reduced motion the error fades in without the 3px drop.
Props
Input
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Height from the control scale: 30, 36 or 44px, the same as buttons. |
icon | ReactNode | None | Leading icon. Muted, it wakes up while the field has focus. |
prefix | ReactNode | None | Leading text set tight against the value, such as £ or https://. |
suffix | ReactNode | None | Trailing unit, status or small button. |
invalid | boolean | None | Red edge and aria-invalid. Inside a Field, follows the Field's error unless set. |
readOnly | boolean | None | Sinks into the well; still focusable and copyable. |
disabled | boolean | None | Sinks into the well and dims; skipped by Tab. |
Every other native input attribute, and the ref, go to the input. className and style go to the frame around it.
Field
| Prop | Type | Default | Description |
|---|---|---|---|
labelRequired | ReactNode | None | The visible label above the control. |
hint | ReactNode | None | Help below the control. Hidden while there is an error. |
error | ReactNode | None | What is wrong and how to fix it. Replaces the hint and marks the control invalid. |
id | string | None | Id for the control inside. Generated when omitted. |
childrenRequired | ReactNode | None | One control: Input, SearchField, or anything reading useFieldControl(). |
Other attributes go to the Field's outer element.