Loading Developer Playground

Loading ...

Skip to main content
ARIA ROLEWidget 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