aria-required
Indicates that user input is required on the element before a form may be submitted. Essential for accessible form validation and helping screen reader users understand mandatory fields.
Overview
The aria-required attribute indicates that user input is required on the element before a form may be submitted. Screen readers announce "required" when users focus on these fields.
Unlike the HTML required attribute, aria-required does NOT trigger browser validation. It only communicates the requirement to assistive technologies. For native form elements, use both attributes together.
When to Use aria-required
Use aria-required for custom widgets (comboboxes, custom selects, etc.) where the HTML required attribute isn't applicable. For native inputs, use required (which automatically sets aria-required).
Live Demo: Required Form Fields
Screen reader announcement: When focusing on the Name field: "Full Name, required, edit text". For Phone: "Phone Number, edit text" (no "required" announcement).
Attribute Values
trueUser input is required on this element before form submission. Screen readers announce "required" when the user focuses on this field.
false(default)The element is optional. This is the default when the attribute is not present. No "required" announcement is made.
required vs aria-required
required(HTML attribute)
- • Triggers browser validation
- • Shows native error messages
- • Prevents form submission if empty
- • Automatically sets aria-required
- • Only works on form elements
Use when: You want browser-native validation on standard form elements.
aria-required="true"(ARIA attribute)
- • Only announces to screen readers
- • NO browser validation
- • Allows form submission
- • Must validate in JavaScript
- • Works on any element
Use when: Building custom widgets or need custom validation.
Code Examples
Basic Usage
<!-- Basic aria-required usage -->
<form>
<label for="name">
Full Name <span aria-hidden="true">*</span>
</label>
<input
type="text"
id="name"
aria-required="true"
/>
<label for="email">
Email <span aria-hidden="true">*</span>
</label>
<input
type="email"
id="email"
aria-required="true"
/>
<label for="phone">Phone (optional)</label>
<input
type="tel"
id="phone"
aria-required="false"
/>
<button type="submit">Submit</button>
</form>
<!-- Screen reader announces: "Full Name, required, edit text" -->required vs aria-required
<!-- aria-required vs HTML required attribute -->
<!-- HTML required attribute -->
<input type="text" required />
<!--
• Browser enforces validation
• Shows built-in error messages
• Prevents form submission
• Announces "required" to screen readers
-->
<!-- aria-required attribute -->
<input type="text" aria-required="true" />
<!--
• ONLY announces "required" to screen readers
• NO browser validation
• Allows form submission
• Must implement validation in JavaScript
-->
<!-- Best Practice: Use BOTH together -->
<input
type="text"
required
aria-required="true"
/>
<!-- Provides both browser validation AND clear AT support -->Custom Widgets
<!-- Custom widgets need aria-required -->
<!-- Custom combobox -->
<div
role="combobox"
aria-required="true"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="country-label"
tabindex="0"
>
<span id="country-label">Country</span>
<span>Select a country</span>
</div>
<!-- Custom listbox -->
<div
role="listbox"
aria-required="true"
aria-labelledby="role-label"
tabindex="0"
>
<span id="role-label">Role</span>
<div role="option">Admin</div>
<div role="option">User</div>
<div role="option">Guest</div>
</div>
<!-- Custom radio group -->
<div
role="radiogroup"
aria-required="true"
aria-labelledby="size-label"
>
<span id="size-label">Size (required)</span>
<div role="radio" aria-checked="false">Small</div>
<div role="radio" aria-checked="false">Medium</div>
<div role="radio" aria-checked="false">Large</div>
</div>Visual Indicators
<!-- Visual indicators for required fields -->
<!-- Using asterisk (hide from screen readers) -->
<label for="email">
Email Address
<span aria-hidden="true" class="required-indicator">*</span>
</label>
<input
type="email"
id="email"
required
aria-required="true"
/>
<!-- Using text (visible to all) -->
<label for="name">
Full Name <span class="required-text">(required)</span>
</label>
<input
type="text"
id="name"
required
aria-required="true"
/>
<!-- Form-level instruction -->
<p class="form-instructions">
Fields marked with <span aria-hidden="true">*</span>
<span class="sr-only">an asterisk</span> are required.
</p>
<style>
.required-indicator {
color: #dc2626;
margin-left: 0.25rem;
}
.required-text {
color: #6b7280;
font-size: 0.875rem;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
</style>React Component
// React Form with Required Fields
import { useState } from 'react';
function RequiredInput({
label,
type = 'text',
required = false,
error,
...props
}) {
const inputId = props.id || props.name;
const errorId = `${inputId}-error`;
return (
<div className="form-field">
<label htmlFor={inputId}>
{label}
{required && (
<span aria-hidden="true" className="required-star">*</span>
)}
</label>
<input
{...props}
type={type}
id={inputId}
required={required}
aria-required={required}
aria-invalid={!!error}
aria-describedby={error ? errorId : undefined}
className={error ? 'input-error' : ''}
/>
{error && (
<span id={errorId} className="error-message" role="alert">
{error}
</span>
)}
</div>
);
}
// Form Component
function ContactForm() {
const [errors, setErrors] = useState({});
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const validate = () => {
const newErrors = {};
if (!formData.name.trim()) {
newErrors.name = 'Name is required';
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Invalid email format';
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
// Submit form
console.log('Form submitted:', formData);
}
};
return (
<form onSubmit={handleSubmit} noValidate>
<p className="form-note">
<span aria-hidden="true">*</span> Required fields
</p>
<RequiredInput
name="name"
label="Full Name"
required
value={formData.name}
onChange={(e) => setFormData({...formData, name: e.target.value})}
error={errors.name}
/>
<RequiredInput
name="email"
type="email"
label="Email Address"
required
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
error={errors.email}
/>
<RequiredInput
name="message"
label="Message"
required
value={formData.message}
onChange={(e) => setFormData({...formData, message: e.target.value})}
error={errors.message}
/>
<button type="submit">Send Message</button>
</form>
);
}Best Practices
Use both required and aria-required on native form elements for maximum compatibility
Provide visual indication of required fields (asterisk, "required" text, etc.)
Include a form-level instruction explaining required field indicators
Use aria-required on custom widgets where HTML required doesn't apply
Implement proper validation and error messaging for required fields
Hide decorative asterisks from screen readers with aria-hidden="true"
Don't rely only on aria-required for validation—implement JavaScript checks
Don't use color alone to indicate required fields—add text or symbols
Don't forget to provide error messages when required fields are empty
Don't make too many fields required—consider what's truly necessary

