Loading Developer Playground

Loading ...

Skip to main content
ARIA ATTRIBUTERelationship Attributes

aria-describedby

Identifies the element (or elements) that describes the current element. Provides additional context or instructions that complement the accessible name.

Value Type
ID Reference (List)
Global Attribute
Yes
Common Use
Forms, Dialogs, Tooltips

Overview

The aria-describedby attribute identifies the element(s) that describe the current element. The description is announced by screen readers after the element's name and role, providing additional context.

Unlike aria-labelledby which provides the accessible name, aria-describedby provides supplementary information. It's commonly used for form field hints, error messages, and detailed descriptions.

Multiple IDs can be space-separated, and the descriptions are concatenated in the order they appear.

Live Demo: Form with Descriptions

We'll never share your email with anyone else.

Must be at least 8 characters with one number.

Try it: Enter a short password and tab away to see the error message.
aria-describedby="password-requirements"

aria-describedby connects form fields to their hints and error messages. Screen readers announce these descriptions after the field name, helping users understand what's expected.

Code Examples

Basic Usage

<!-- aria-describedby references elements that describe the current element -->

<!-- Form field with hint text -->
<label for="email">Email Address</label>
<input
  type="email"
  id="email"
  aria-describedby="email-hint"
/>
<p id="email-hint">
  We'll never share your email with anyone else.
</p>

<!-- Screen reader announces: "Email Address, edit text, 
     We'll never share your email with anyone else." -->

Error Messages

<!-- Form validation with aria-describedby -->

<label for="password">Password</label>
<input
  type="password"
  id="password"
  aria-describedby="password-requirements password-error"
  aria-invalid="true"
/>

<!-- Requirements (always shown) -->
<p id="password-requirements">
  Password must be at least 8 characters with one number.
</p>

<!-- Error message (shown when invalid) -->
<p id="password-error" class="error" role="alert">
  Password is too short.
</p>

<!-- Multiple IDs can be space-separated -->
<!-- Screen reader announces both descriptions -->

Multiple Descriptions

<!-- Combining multiple descriptions -->

<button
  aria-describedby="btn-shortcut btn-tooltip"
>
  Save Document
</button>

<span id="btn-shortcut" class="sr-only">
  Keyboard shortcut: Ctrl+S
</span>
<span id="btn-tooltip" class="sr-only">
  Saves the current document to the cloud
</span>

<!-- Both descriptions are announced together -->

React Components

// React form with aria-describedby
import { useState, useId } from 'react';

interface FormFieldProps {
  label: string;
  type?: string;
  hint?: string;
  error?: string;
  value: string;
  onChange: (value: string) => void;
}

function FormField({
  label,
  type = 'text',
  hint,
  error,
  value,
  onChange
}: FormFieldProps) {
  const id = useId();
  const hintId = `${id}-hint`;
  const errorId = `${id}-error`;
  
  // Build aria-describedby from available descriptions
  const describedBy = [
    hint && hintId,
    error && errorId,
  ].filter(Boolean).join(' ') || undefined;

  return (
    <div className="form-field">
      <label htmlFor={id}>{label}</label>
      
      <input
        id={id}
        type={type}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        aria-describedby={describedBy}
        aria-invalid={!!error}
      />
      
      {hint && (
        <p id={hintId} className="hint">
          {hint}
        </p>
      )}
      
      {error && (
        <p id={errorId} className="error" role="alert">
          {error}
        </p>
      )}
    </div>
  );
}

// Password field with requirements
function PasswordField({ value, onChange, error }) {
  const id = useId();
  const requirementsId = `${id}-requirements`;
  const errorId = `${id}-error`;
  
  const describedBy = [
    requirementsId,
    error && errorId,
  ].filter(Boolean).join(' ');

  return (
    <div>
      <label htmlFor={id}>Password</label>
      
      <input
        id={id}
        type="password"
        value={value}
        onChange={(e) => onChange(e.target.value)}
        aria-describedby={describedBy}
        aria-invalid={!!error}
      />
      
      <ul id={requirementsId} className="requirements">
        <li>At least 8 characters</li>
        <li>One uppercase letter</li>
        <li>One number</li>
      </ul>
      
      {error && (
        <p id={errorId} className="error" role="alert">
          {error}
        </p>
      )}
    </div>
  );
}

// Dialog with description
function Dialog({ title, description, children, onClose }) {
  const titleId = useId();
  const descId = useId();

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby={titleId}
      aria-describedby={descId}
    >
      <h2 id={titleId}>{title}</h2>
      <p id={descId}>{description}</p>
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}

Related Attributes

Specifications & Resources