Loading Developer Playground

Loading ...

Skip to main content
ARIA ROLE•Widget Roles

textbox

An input that allows free-form text as its value. Textboxes can be single-line like an input field or multi-line like a textarea, and may support rich text editing when used with contenteditable.

Native HTML Equivalents
<input>, <textarea>
Multi-line Support
aria-multiline
Subclass Roles
searchbox, combobox

Overview

The textbox role identifies an element as an input field that accepts free-form text. By default, textboxes are single-line (like <input type="text">). Add aria-multiline="true" for multi-line behavior (like <textarea>).

Textboxes are commonly implemented using contenteditable elements for custom styling or rich text editing. However, native HTML inputs are preferred for most use cases due to built-in accessibility features.

Native Elements vs role="textbox"

Always prefer native <input> and <textarea> elements. They provide automatic keyboard support, form integration, autocomplete, mobile keyboard optimization, and proper validation. Use role="textbox" only for custom implementations like rich text editors.

Live Demo: Text Input Types

Single-line Textbox

Multi-line Textbox (textarea)

Write a brief description0/200

Custom Textbox (contenteditable)

Note: The native <input> and <textarea> elements provide the best accessibility. The contenteditable example demonstrates the textbox role but requires additional work for full accessibility.

Code Examples

Basic Textbox

<!-- Basic Textbox -->
<label for="username">Username</label>
<div
  role="textbox"
  id="username"
  contenteditable="true"
  aria-labelledby="username-label"
></div>

<!-- Note: For most cases, use native <input> instead -->
<label for="username-native">Username</label>
<input type="text" id="username-native" />

Native HTML Inputs (Preferred)

<!-- Native HTML Text Inputs (Preferred) -->

<!-- Single-line text input -->
<label for="email">Email Address</label>
<input 
  type="email" 
  id="email"
  placeholder="you@example.com"
  autocomplete="email"
  required
/>

<!-- Multi-line text input -->
<label for="bio">Biography</label>
<textarea 
  id="bio"
  rows="4"
  placeholder="Tell us about yourself..."
></textarea>

<!-- Password input -->
<label for="password">Password</label>
<input 
  type="password" 
  id="password"
  autocomplete="current-password"
/>

<!-- Benefits of native inputs:
     - Built-in keyboard support
     - Form validation
     - Autocomplete support
     - Mobile keyboard optimization
     - Browser autofill -->

Multi-line Textbox

<!-- Multi-line Textbox (aria-multiline) -->
<label id="comment-label">Your Comment</label>
<div
  role="textbox"
  aria-labelledby="comment-label"
  aria-multiline="true"
  contenteditable="true"
  class="multiline-textbox"
>
</div>

<!-- Native equivalent (preferred) -->
<label for="comment">Your Comment</label>
<textarea 
  id="comment"
  rows="5"
  cols="40"
></textarea>

<!-- aria-multiline="true" indicates the textbox
     accepts multiple lines of text, like <textarea>.
     Without it, textbox is assumed single-line like <input> -->

Rich Text Editor

<!-- Rich Text Editor -->
<div class="rich-editor">
  <div role="toolbar" aria-label="Text formatting">
    <button aria-label="Bold" aria-pressed="false">B</button>
    <button aria-label="Italic" aria-pressed="false">I</button>
    <button aria-label="Underline" aria-pressed="false">U</button>
  </div>
  
  <div
    role="textbox"
    aria-label="Rich text editor"
    aria-multiline="true"
    aria-describedby="editor-instructions"
    contenteditable="true"
    class="editor-content"
  >
    <!-- Rich text content here -->
  </div>
  
  <div id="editor-instructions" class="sr-only">
    Use toolbar buttons to format text. 
    Press Tab to exit the editor.
  </div>
</div>

<!-- Note: Rich text editors require significant 
     additional accessibility implementation -->

Validation Pattern

<!-- Textbox with Validation -->
<div class="form-field">
  <label for="email" id="email-label">
    Email <span aria-hidden="true">*</span>
  </label>
  
  <input
    type="email"
    id="email"
    aria-labelledby="email-label"
    aria-required="true"
    aria-invalid="true"
    aria-describedby="email-error email-hint"
    value="invalid-email"
  />
  
  <span id="email-hint" class="hint">
    Enter a valid email address
  </span>
  
  <span id="email-error" class="error" role="alert">
    Please enter a valid email address
  </span>
</div>

<script>
  const emailInput = document.getElementById('email');
  const errorSpan = document.getElementById('email-error');
  
  emailInput.addEventListener('blur', () => {
    const isValid = emailInput.validity.valid;
    emailInput.setAttribute('aria-invalid', !isValid);
    errorSpan.hidden = isValid;
  });
</script>

<style>
  [aria-invalid="true"] {
    border-color: #dc2626;
  }
  
  .error {
    color: #dc2626;
    font-size: 0.875rem;
  }
</style>

React Component

// React Textbox Component
import { useState, useRef, useId } from 'react';

interface TextboxProps {
  label: string;
  value: string;
  onChange: (value: string) => void;
  multiline?: boolean;
  rows?: number;
  placeholder?: string;
  required?: boolean;
  error?: string;
  hint?: string;
  maxLength?: number;
}

function Textbox({
  label,
  value,
  onChange,
  multiline = false,
  rows = 4,
  placeholder,
  required = false,
  error,
  hint,
  maxLength,
}: TextboxProps) {
  const inputId = useId();
  const hintId = useId();
  const errorId = useId();

  const describedBy = [
    hint && hintId,
    error && errorId,
  ].filter(Boolean).join(' ') || undefined;

  const inputProps = {
    id: inputId,
    value,
    onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => 
      onChange(e.target.value),
    placeholder,
    'aria-required': required || undefined,
    'aria-invalid': !!error || undefined,
    'aria-describedby': describedBy,
    maxLength,
    className: `textbox ${error ? 'error' : ''}`,
  };

  return (
    <div className="textbox-wrapper">
      <label htmlFor={inputId}>
        {label}
        {required && <span aria-hidden="true"> *</span>}
      </label>

      {multiline ? (
        <textarea {...inputProps} rows={rows} />
      ) : (
        <input type="text" {...inputProps} />
      )}

      {hint && !error && (
        <span id={hintId} className="hint">
          {hint}
        </span>
      )}

      {error && (
        <span id={errorId} className="error-message" role="alert">
          {error}
        </span>
      )}

      {maxLength && (
        <span className="char-count" aria-live="polite">
          {value.length}/{maxLength}
        </span>
      )}
    </div>
  );
}

// Usage
function Form() {
  const [name, setName] = useState('');
  const [bio, setBio] = useState('');
  const [email, setEmail] = useState('');

  return (
    <form>
      <Textbox
        label="Full Name"
        value={name}
        onChange={setName}
        required
        hint="Enter your legal name"
      />

      <Textbox
        label="Biography"
        value={bio}
        onChange={setBio}
        multiline
        rows={5}
        maxLength={500}
        placeholder="Tell us about yourself..."
      />

      <Textbox
        label="Email"
        value={email}
        onChange={setEmail}
        required
        error={email && !email.includes('@') ? 'Invalid email' : ''}
      />
    </form>
  );
}

Autocomplete Pattern

<!-- Textbox with Autocomplete -->
<div class="autocomplete-container">
  <label for="city-input" id="city-label">City</label>
  
  <input
    type="text"
    id="city-input"
    role="combobox"
    aria-labelledby="city-label"
    aria-autocomplete="list"
    aria-controls="city-listbox"
    aria-expanded="true"
    aria-activedescendant="city-option-2"
    autocomplete="off"
  />
  
  <ul 
    id="city-listbox"
    role="listbox"
    aria-label="City suggestions"
  >
    <li role="option" id="city-option-1" aria-selected="false">
      New York
    </li>
    <li role="option" id="city-option-2" aria-selected="true">
      Los Angeles
    </li>
    <li role="option" id="city-option-3" aria-selected="false">
      Chicago
    </li>
  </ul>
</div>

<!-- Note: When adding autocomplete to a textbox,
     use role="combobox" instead of role="textbox"
     for proper semantics -->

Keyboard Support

Native inputs handle keyboard automatically. Custom textboxes must implement:

Any character
→Types the character at cursor position

Standard text input

Backspace
→Deletes character before cursor

Or selected text

Delete
→Deletes character after cursor

Or selected text

←→
→Moves cursor one character

Add Ctrl for word navigation

↑↓
→Moves cursor one line (multiline)

Only in multi-line textboxes

Home
→Moves cursor to start of line

Ctrl+Home for start of field

End
→Moves cursor to end of line

Ctrl+End for end of field

Enter
→New line (multiline) or submit (single)

Behavior depends on context

Best Practices

✓

Use native <input> and <textarea> elements whenever possible

✓

Always provide visible labels using <label> elements

✓

Use aria-required for required fields and aria-invalid for validation

✓

Provide helpful placeholder text and aria-describedby hints

✓

Use appropriate input types (email, tel, url) for specialized data

✓

Support autocomplete attributes for common fields

×

Don't use role="textbox" when native inputs work

×

Don't rely solely on placeholder text as labels

×

Don't forget to announce validation errors to screen readers

×

Don't use contenteditable without proper ARIA implementation

Supported ARIA Attributes

aria-multiline

Indicates if textbox accepts multiple lines

aria-label

Accessible name for the textbox

aria-labelledby

References element(s) that label the textbox

aria-describedby

References hints, errors, or instructions

aria-required

Indicates if input is required

aria-invalid

Indicates validation state (true/false)

aria-readonly

Indicates if textbox is read-only

aria-disabled

Indicates if textbox is disabled

aria-placeholder

Visible placeholder hint text

aria-autocomplete

Type of autocomplete support

Common Use Cases

Login/registration forms
Contact forms and messaging
Search inputs (use searchbox)
Rich text editors (WYSIWYG)
Code editors
Comment/review inputs
Profile/settings forms
Address and shipping forms

Accessibility Notes

Labels Are Essential

Every textbox needs an accessible label. Use a visible <label> element with the for attribute, or use aria-label/ aria-labelledby. Never rely solely on placeholder text.

Error Handling

Use aria-invalid="true" when validation fails, and ensure error messages are associated via aria-describedby. Consider using role="alert" for dynamically appearing error messages.

Mobile Considerations

Use appropriate input types (type="email", type="tel", etc.) to trigger optimized mobile keyboards. Add inputmode for more control over virtual keyboard layout.

Related Roles & Attributes

Specifications & Resources