aria-placeholder
Defines a short hint intended to aid the user with data entry when the control has no value. Used for custom ARIA widgets where the native HTML placeholder attribute doesn't apply.
Overview
The aria-placeholder attribute defines a short hint (a word or short phrase) intended to help users understand what data should be entered when a form control has no value.
This attribute is specifically for custom ARIA widgets like role="textbox" or role="combobox". For native HTML inputs, use the standard placeholder attribute instead.
Important Warning
Placeholders are NOT a replacement for labels! They disappear when users type, making the field unclear. Always use a visible <label> or aria-label in addition to placeholders.
Live Demo: Custom Search Input
Current value: (empty)
Screen reader announcement: When focused: "Search Products, combobox, collapsed, Type to search by name, SKU, or category..."
When to Use
✓ Use aria-placeholder
- • Custom ARIA textbox widgets
- • Custom combobox implementations
- • Contenteditable elements with roles
- • Rich text editors with ARIA roles
- • Custom search inputs
✗ Don't Use aria-placeholder
- • Native <input> elements (use placeholder)
- • Native <textarea> elements (use placeholder)
- • As a replacement for labels
- • For essential instructions
- • When label alone is sufficient
Code Examples
Basic Usage
<!-- Basic aria-placeholder usage -->
<!-- Custom combobox with placeholder -->
<div
role="combobox"
aria-placeholder="Type to search..."
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="search-label"
contenteditable="true"
tabindex="0"
>
<!-- Empty - shows placeholder -->
</div>
<!-- Custom textbox with placeholder -->
<div
role="textbox"
aria-placeholder="Enter your message here"
aria-multiline="true"
contenteditable="true"
tabindex="0"
>
</div>
<!-- Screen reader announcement: "Search, combobox, collapsed, Type to search..." -->aria-placeholder vs placeholder
<!-- aria-placeholder vs HTML placeholder attribute -->
<!-- HTML placeholder (for native inputs) -->
<input
type="text"
placeholder="Enter your name"
aria-label="Full name"
/>
<!--
• Native browser support
• Automatically announced by screen readers
• Works only on <input> and <textarea>
• No aria-placeholder needed!
-->
<!-- aria-placeholder (for custom widgets) -->
<div
role="textbox"
aria-placeholder="Enter your name"
aria-label="Full name"
contenteditable="true"
tabindex="0"
>
</div>
<!--
• For ARIA widgets only
• Provides placeholder hint to AT
• Used with contenteditable elements
• Required for custom input widgets
-->
<!-- WARNING: Don't use both together on native inputs -->
<input
type="text"
placeholder="Enter name"
aria-placeholder="Enter name" <!-- Redundant! -->
/>Custom Search Input
<!-- Custom search combobox with placeholder -->
<div class="custom-search" role="search">
<label id="search-label" for="search-input">Search Products</label>
<div
id="search-input"
role="combobox"
aria-placeholder="Search by name, SKU, or category..."
aria-expanded="false"
aria-autocomplete="list"
aria-haspopup="listbox"
aria-controls="search-results"
aria-labelledby="search-label"
contenteditable="true"
tabindex="0"
></div>
<ul
id="search-results"
role="listbox"
aria-label="Search suggestions"
hidden
>
<!-- Results populated dynamically -->
</ul>
</div>
<style>
/* Show placeholder when empty */
[role="combobox"]:empty::before {
content: attr(aria-placeholder);
color: #9ca3af;
pointer-events: none;
}
</style>Accessibility Considerations
<!-- Important: Placeholders are NOT labels! -->
<!-- BAD: Using placeholder as the only label -->
<div
role="textbox"
aria-placeholder="Email address" <!-- Not sufficient! -->
contenteditable="true"
>
</div>
<!-- GOOD: Using proper label + placeholder -->
<label id="email-label">Email Address</label>
<div
role="textbox"
aria-labelledby="email-label"
aria-placeholder="name@example.com"
aria-required="true"
contenteditable="true"
>
</div>
<!-- Remember: -->
<!-- • Placeholders disappear when user types -->
<!-- • Some users don't see placeholders at all -->
<!-- • Labels are persistent and always visible -->
<!-- • Use placeholder for FORMAT hints, not instructions -->React Component
// React Custom Textbox with aria-placeholder
import { useState, useRef, useEffect } from 'react';
function CustomTextbox({
label,
placeholder,
onChange,
value,
id,
...props
}) {
const textboxRef = useRef(null);
const [isEmpty, setIsEmpty] = useState(!value);
useEffect(() => {
// Keep contenteditable in sync with value prop
if (textboxRef.current && textboxRef.current.textContent !== value) {
textboxRef.current.textContent = value || '';
}
setIsEmpty(!value);
}, [value]);
const handleInput = (e) => {
const newValue = e.target.textContent;
setIsEmpty(!newValue);
onChange?.(newValue);
};
return (
<div className="form-field">
<label id={`${id}-label`} htmlFor={id}>
{label}
</label>
<div
ref={textboxRef}
id={id}
role="textbox"
aria-labelledby={`${id}-label`}
aria-placeholder={placeholder}
contentEditable
tabIndex={0}
onInput={handleInput}
className={`custom-textbox ${isEmpty ? 'empty' : ''}`}
{...props}
/>
<style>{`
.custom-textbox {
min-height: 40px;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
outline: none;
}
.custom-textbox:focus {
border-color: #6366f1;
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
}
.custom-textbox.empty::before {
content: attr(aria-placeholder);
color: #9ca3af;
pointer-events: none;
}
`}</style>
</div>
);
}
// Usage
function SearchForm() {
const [query, setQuery] = useState('');
return (
<CustomTextbox
id="search"
label="Search"
placeholder="Type to search products..."
value={query}
onChange={setQuery}
/>
);
}Best Practices
Always provide a visible label in addition to aria-placeholder
Use placeholder for format hints (e.g., "mm/dd/yyyy") not instructions
Keep placeholder text short and helpful
Use CSS to visually display placeholder when content is empty
Ensure sufficient color contrast for placeholder text
Don't use placeholder as the only label—it disappears when typing
Don't put essential information only in placeholder text
Don't use on native inputs—use HTML placeholder attribute instead
Don't make placeholder text too long—keep it concise

