Text Input Field
An accessible text input component with label, hint text, error display, success indicator, and comprehensive state management. Foundation for all form inputs with full keyboard and screen reader support.
Overview
The Text Input component provides a fully accessible form input field that handles all common states: default, focus, error, success, disabled, and readonly. It includes proper labeling, ARIA attributes, keyboard navigation, and screen reader support.
Component Structure
Container
├── Label
│ ├── Label Text
│ ├── Required Indicator (*) [optional]
│ └── Tooltip [optional]
├── Input Wrapper
│ ├── Leading Icon [optional]
│ ├── Input Field
│ └── Trailing Icon/Button [optional]
├── Hint Text [optional]
├── Error Message [optional]
└── Character Counter [optional]
States
Default State
- Clean, clear input field
- Border: neutral gray (
border-gray-300) - Background: white
- Cursor: text cursor
Focus State
- Border changes to focus color (
border-blue-500) - Focus ring appears (
ring-2 ring-blue-500) - Smooth transition (150ms)
- Label optionally changes to primary color
Error State
- Border changes to error color (
border-red-500) - Error message appears below input
- Error icon shown
aria-invalid='true'set- Optional: subtle shake animation
Success State
- Border changes to success color (
border-green-500) - Checkmark icon appears (trailing)
aria-invalid='false'set- Validates field is correct
Disabled State
- Background grayed out (
bg-gray-50) - Text grayed (
text-gray-500) - Cursor: not-allowed
- Not interactive
- Label grayed out
Readonly State
- Input not editable
- Can be selected and copied
- Visually distinct from disabled
- Still keyboard accessible
Required Props
{
label: string; // Input label text
name: string; // Input name attribute
}
Optional Props
{
id?: string; // Input ID (generated if not provided)
value?: string; // Controlled value
defaultValue?: string; // Uncontrolled default value
placeholder?: string; // Placeholder text
type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'search';
error?: string | null; // Error message
success?: boolean; // Success state
hint?: string; // Hint text below input
required?: boolean; // Required field
disabled?: boolean; // Disabled state
readonly?: boolean; // Readonly state
size?: 'small' | 'medium' | 'large';
leadingIcon?: ReactNode; // Icon before input
trailingIcon?: ReactNode; // Icon after input
maxLength?: number; // Max character length
showCharacterCount?: boolean; // Show character counter
fullWidth?: boolean; // Full width (default: true)
onChange?: (value: string) => void;
onBlur?: () => void;
onFocus?: () => void;
autoComplete?: string;
autoFocus?: boolean;
}
Size Variants
Small
- Height: 36px
- Padding: 12px horizontal, 8px vertical
- Font size: 14px
- Label font size: 13px
Medium (Default)
- Height: 44px
- Padding: 14px horizontal, 11px vertical
- Font size: 16px
- Label font size: 14px
Large
- Height: 52px
- Padding: 16px horizontal, 14px vertical
- Font size: 18px
- Label font size: 16px
Common Variations
Search Input
<TextInput
label="Search"
name="search"
type="search"
placeholder="Search..."
leadingIcon={<SearchIcon />}
trailingIcon={value ? <ClearButton /> : null}
/>
Password Input
<TextInput
label="Password"
name="password"
type={showPassword ? 'text' : 'password'}
required
trailingIcon={<PasswordToggle />}
/>
Email Input with Validation
<TextInput
label="Email Address"
name="email"
type="email"
required
error={errors.email}
success={isValid.email}
hint="We'll never share your email"
leadingIcon={<AtIcon />}
/>
Phone Input
<TextInput
label="Phone Number"
name="phone"
type="tel"
placeholder="(555) 123-4567"
leadingIcon={<PhoneIcon />}
hint="Include area code"
/>
Accessibility Requirements
ARIA Attributes
Input Element:
<input
id="email"
name="email"
type="email"
aria-labelledby="email-label"
aria-describedby="email-hint email-error"
aria-invalid="true"
aria-required="true"
/>
Label:
<label id="email-label" for="email">
Email Address
<span aria-hidden="true">*</span>
</label>
Error Message:
<div id="email-error" role="alert" aria-live="polite">
Please enter a valid email address
</div>
Hint Text:
<div id="email-hint">
We'll never share your email with anyone else
</div>
Keyboard Navigation
| Key | Action | |-----|--------| | Tab | Focus input | | Shift+Tab | Move focus away | | Escape | Clear value (optional) | | Enter | Submit form (if in form) |
Screen Reader Behavior
When input receives focus, screen reader announces:
- Label text ("Email Address")
- Required state ("required")
- Hint text ("We'll never share your email")
- Error message (if present)
- Current value (if any)
When error appears:
- Announced via
aria-live='polite' - User hears error without losing context
Responsive Behavior
Mobile (< 768px)
- Full width by default
- Font size: 16px minimum (prevents iOS zoom)
- Touch targets: 44px minimum
- Trailing icons: 44px tap target
Desktop (>= 1024px)
- Fixed or constrained width based on context
- Hover state shows border color change
- Default: 100% of parent width
Implementation Checklist
Visual
- [ ] Label displays above input
- [ ] Required indicator (*) shows when required
- [ ] Hint text displays below input
- [ ] Error message displays below input with icon
- [ ] Success checkmark displays when valid
- [ ] Focus ring visible and distinct
- [ ] Disabled state is visually clear
- [ ] All size variants work correctly
Functionality
- [ ] Value updates on input change
- [ ] onChange callback fires
- [ ] onBlur callback fires
- [ ] onFocus callback fires
- [ ] Clear button clears value
- [ ] Password toggle changes type
- [ ] Character counter updates
- [ ] Controlled and uncontrolled modes work
Keyboard
- [ ] Tab focuses input
- [ ] Shift+Tab moves focus away
- [ ] Trailing button is keyboard accessible
- [ ] Escape clears value (if implemented)
Accessibility
- [ ] Label associated with input (for/id)
- [ ] aria-required when required
- [ ] aria-invalid when error
- [ ] aria-describedby references hint and error
- [ ] Error has role='alert' or aria-live
- [ ] Focus ring has sufficient contrast (3:1)
- [ ] Screen reader announces all states
- [ ] Input font size >= 16px on mobile
Styling
- [ ] Matches project styling system
- [ ] Uses project design tokens
- [ ] No inline styles (uses classes)
- [ ] All colors have sufficient contrast
Framework Examples
React (Controlled)
const [email, setEmail] = useState('');
const [error, setError] = useState(null);
<TextInput
label="Email"
name="email"
type="email"
value={email}
onChange={setEmail}
error={error}
required
/>
Vue
<template>
<TextInput
v-model="email"
label="Email"
name="email"
type="email"
:error="errors.email"
:required="true"
/>
</template>
Angular
emailControl = new FormControl('', [Validators.required, Validators.email]);
<app-text-input
[label]="'Email'"
[formControl]="emailControl"
[error]="emailControl.errors?.email"
[required]="true"
></app-text-input>
Svelte
<script>
let email = '';
let error = null;
</script>
<TextInput
bind:value={email}
label="Email"
name="email"
type="email"
{error}
required
/>
Integration with Form Validation Pattern
This Text Input component is designed to work seamlessly with the Form Validation Pattern:
- Validation Timing: Show errors only after
onBlur(when touched) - Error Display: Pass error message via
errorprop - Success State: Pass
successprop when field is validated - ARIA: Errors are announced via
aria-live - Keyboard: Full keyboard support for accessible forms
Design Tokens
Colors
- Border Default:
--color-gray-300orborder-gray-300 - Border Focus:
--color-blue-500orborder-blue-500 - Border Error:
--color-red-500orborder-red-500 - Border Success:
--color-green-500orborder-green-500 - Background:
--color-whiteorbg-white - Background Disabled:
--color-gray-50orbg-gray-50 - Text:
--color-gray-900ortext-gray-900 - Text Disabled:
--color-gray-500ortext-gray-500 - Label:
--color-gray-700ortext-gray-700 - Hint:
--color-gray-500ortext-gray-500 - Error:
--color-red-600ortext-red-600
Spacing
- Label margin-bottom: 6px
- Hint margin-top: 6px
- Error margin-top: 6px
- Input padding-x: 14px (medium)
- Input padding-y: 11px (medium)
Typography
- Label font-size: 14px
- Label font-weight: 500
- Input font-size: 16px
- Hint font-size: 14px
- Error font-size: 14px
See Also
The Text Input component works well with these potions:
- Dropdown/Select Component - Complementary form component for selection fields, works alongside text inputs in forms
- Form Validation Pattern - Apply validation timing and error display best practices to create user-friendly forms
- Login & Registration Forms - Complete authentication forms that demonstrate Text Input in action
These are suggestions to enhance your implementation. Text Input works independently and can be used in any context.
Summary for AI Agents
Goal: Create accessible text input with label, error/success states, hint text, and ARIA support.
Critical Requirements:
- Detect framework and styling system first
- Use project's design tokens for colors and spacing
- Implement proper ARIA:
aria-labelledby,aria-describedby,aria-invalid,aria-required - Show required indicator (*) when
required=true - Associate errors via
aria-describedbyandrole='alert' - Provide visible focus ring with sufficient contrast
- Set input font-size to 16px minimum on mobile (prevents iOS zoom)
- Support controlled/uncontrolled patterns appropriate for framework
- Make trailing icons keyboard accessible with
aria-label - Use classes, not inline styles
States to Handle: default, focus, error, success, disabled, readonly
Integration: Works with Form Validation Pattern for optimal UX timing.