A 'show password' toggle button that changes an input from type="password" to type="text" — but has no aria-pressed — is announced identically by screen readers whether the password is visible or hidden. Users cannot tell whether their action succeeded. The same problem applies to a favorites button, a bookmark toggle, a mute button, and any other binary-state control. WCAG 2.2 SC 4.1.2 (Name, Role, Value) requires that the current value or state of UI components be programmatically determinable. Section 508 2018 Refresh 502.3.4 mandates that value or state changes be programmatically determinable. The expected pattern per ARIA Authoring Practices: a toggle button uses aria-pressed to expose its binary state.
Medium because missing `aria-pressed` leaves screen reader users unable to determine toggle state, requiring them to infer it from side effects rather than direct feedback.
Add aria-pressed to every toggle button and update it synchronously with the state change. Combine with a descriptive aria-label that either reflects the current state or describes the action.
// Show/hide password toggle
const [isVisible, setIsVisible] = useState(false);
<div className="relative">
<input
id="password"
type={isVisible ? 'text' : 'password'}
aria-describedby="password-hint"
/>
<button
type="button"
aria-pressed={isVisible}
aria-label={isVisible ? 'Hide password' : 'Show password'}
onClick={() => setIsVisible(v => !v)}
className="absolute right-2 top-1/2 -translate-y-1/2"
>
{isVisible ? <EyeOff aria-hidden="true" /> : <Eye aria-hidden="true" />}
</button>
</div>
// Favorites button
<button
type="button"
aria-pressed={isFavorited}
aria-label={isFavorited ? 'Remove from favorites' : 'Add to favorites'}
onClick={() => setIsFavorited(v => !v)}
>
<HeartIcon
aria-hidden="true"
fill={isFavorited ? 'currentColor' : 'none'}
/>
</button>
Do not use aria-checked for buttons — that is for checkboxes and radio buttons. aria-pressed is the correct attribute for toggle buttons.
ID: accessibility-basics.forms-interactive.toggle-state
Severity: medium
What to look for: Enumerate every relevant item. Check buttons that toggle a state (like show/hide password, theme toggle, favorites). These should use aria-pressed attribute to communicate their on/off state to screen readers.
Pass criteria: At least 1 of the following conditions is met. Toggle buttons have aria-pressed="true" or aria-pressed="false" attribute. The attribute updates when the state changes.
Fail criteria: Toggle buttons lack aria-pressed, or the attribute is not updated on state change.
Skip (N/A) when: The application has no toggle buttons.
Detail on fail: Identify toggle buttons without aria-pressed. Example: "Show password toggle button has no aria-pressed. Theme switcher button has no state indication."
Remediation: Add aria-pressed to toggle buttons:
const ThemeToggle = ({ isDark, setIsDark }) => (
<button
aria-pressed={isDark}
onClick={() => setIsDark(!isDark)}
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
>
{isDark ? '🌙' : '☀️'}
</button>
);