Input
A single-line hand-drawn text field with an optional label and error message.
Overview
A single-line text field for collecting a piece of data, with an optional visible label and (on React) an error message.
Anatomy
An optional label above a sketchy-outlined text box; on React, an error message renders below the
box as a role="alert" element when error is set.
Variants & States
| State | Status | Notes |
|---|---|---|
| Default | Implemented | — |
| Focused | Implemented | Stroke color swaps to a focus token. |
| Disabled | Implemented | — |
| Error / invalid | Implemented | React only — aria-invalid + aria-describedby + role="alert". Not implemented on Web Components or React Native. |
| Loading | Not implemented | — |
Web Components instead has required (native constraint validation via ElementInternals) —
React and React Native don’t expose that as a prop at all. These are two separate, real gaps, not
documentation omissions: don’t show an error demo for Web Components or React Native.
Usage
Do
- Always pair error with a specific, actionable message ("Please enter at least 8 characters."), not a generic "Invalid input."
- Keep label visible rather than relying on placeholder text alone.
Don't
- Don't rely on placeholder text as the only accessible name — it disappears once the user types.
- Don't show more than one error message per field.
Accessibility
- React:
aria-invalid+aria-describedby(pointing at the error<span role="alert">) + RadixLabel/htmlForassociation. - Web Components:
label/forassociation;aria-labelfalls back to the placeholder when no label is set. No invalid-state ARIA exists today. - React Native:
accessibilityLabelfalls back toplaceholder;accessibilityState={{disabled}}has no error/invalid concept. - See the Accessibility Guide for general conventions.
Content
The label names the data being collected (“Email”, “Password”), not an action — contrast with Button, whose label names an action. Error messages state what’s wrong and how to fix it.
Props API
React@ghds/react
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible label, associated via Radix Label. |
error | string | — | Error message. When set, marks the field invalid and announces it. |
disabled | boolean | false | Disables editing. |
...rest | InputHTMLAttributes | — | All other native <input> attributes (value, onChange, etc.) pass through. |
Web Components@ghds/web-components
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | '' | Current field value, mirrored to the form value. |
name | string | — | Submitted control name. |
type | 'text' | 'email' | 'password' | 'search' | 'tel' | 'url' | 'number' | 'text' | Input mode. |
placeholder | string | — | Placeholder text. |
label | string | — | Optional visible label, also the accessible name. |
disabled | boolean | false | Disables editing and form participation. |
required | boolean | false | Native constraint validation. No error prop exists. |
React Native@ghds/react-native
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled text value. |
onChangeText | (text: string) => void | — | Change handler. |
placeholder | string | — | Placeholder; also the default a11y label. |
disabled | boolean | false | Disables editing. |
accessibilityLabel | string | — | Falls back to placeholder. |
keyboardType | TextInputProps['keyboardType'] | — | Keyboard variant. |
secureTextEntry | boolean | — | Masks input for passwords. No error prop exists. |
testID | string | — | Test handle for queries. |
Code examples
// React — @ghds/react (onChange receives an event; `error` is React-only)
import { Input } from '@ghds/react/input';
<Input
label="Email"
placeholder="you@example.com"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>;
<Input label="Password" type="password" error="Please enter at least 8 characters." />;
<!-- Web Components — @ghds/web-components (has `required`; no `error`/invalid prop) -->
<script type="module">
import '@ghds/web-components/input';
</script>
<gh-input label="Email" placeholder="you@example.com" type="email"></gh-input>
<gh-input label="Username" required></gh-input>
// React Native — @ghds/react-native (onChangeText receives a bare string; no `error` prop)
import { Input } from '@ghds/react-native/input';
<Input
value={email}
onChangeText={setEmail}
placeholder="you@example.com"
keyboardType="email-address"
/>;
Live Demo
React’s demo includes the error state; the Web Components demo intentionally does not (it has no
error prop) and shows required instead.