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:

  1. Label text ("Email Address")
  2. Required state ("required")
  3. Hint text ("We'll never share your email")
  4. Error message (if present)
  5. 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:

  1. Validation Timing: Show errors only after onBlur (when touched)
  2. Error Display: Pass error message via error prop
  3. Success State: Pass success prop when field is validated
  4. ARIA: Errors are announced via aria-live
  5. Keyboard: Full keyboard support for accessible forms

Design Tokens

Colors

  • Border Default: --color-gray-300 or border-gray-300
  • Border Focus: --color-blue-500 or border-blue-500
  • Border Error: --color-red-500 or border-red-500
  • Border Success: --color-green-500 or border-green-500
  • Background: --color-white or bg-white
  • Background Disabled: --color-gray-50 or bg-gray-50
  • Text: --color-gray-900 or text-gray-900
  • Text Disabled: --color-gray-500 or text-gray-500
  • Label: --color-gray-700 or text-gray-700
  • Hint: --color-gray-500 or text-gray-500
  • Error: --color-red-600 or text-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:

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:

  1. Detect framework and styling system first
  2. Use project's design tokens for colors and spacing
  3. Implement proper ARIA: aria-labelledby, aria-describedby, aria-invalid, aria-required
  4. Show required indicator (*) when required=true
  5. Associate errors via aria-describedby and role='alert'
  6. Provide visible focus ring with sufficient contrast
  7. Set input font-size to 16px minimum on mobile (prevents iOS zoom)
  8. Support controlled/uncontrolled patterns appropriate for framework
  9. Make trailing icons keyboard accessible with aria-label
  10. Use classes, not inline styles

States to Handle: default, focus, error, success, disabled, readonly

Integration: Works with Form Validation Pattern for optimal UX timing.