ARIA directives
$aria adds accessibility-supporting defaults for the ARIA attributes that
AngularTS manages automatically. It preserves authored ARIA, role, and
tabindex attributes and should be treated as framework support for accessible
applications, not as a complete WCAG conformance guarantee.
Use module.config({ $aria: ... }) when you need to adjust global ARIA support.
const app = angular.createModule('app', []);
app.config({
$aria: {
ariaCurrent: true,
ariaCurrentToken: 'page',
bindRoleForState: true,
bindKeydown: true,
diagnostics: false,
},
});
ng.AriaConfig checks the standardized aria-current token used by router
links. ARIA attributes authored in templates remain native HTML rather than
framework-specific TypeScript contracts.
The public attribute-name surface follows the current WAI-ARIA states and properties and the developer-facing MDN ARIA attribute reference.
Use ng-aria-disable on an element when framework-authored ARIA behavior should
not apply locally.
Set diagnostics: true in development or tests when you want $aria to warn
about common authored mistakes on framework-managed interactive elements:
positive tabindex, missing accessible names, missing ARIA reference targets,
and interactive elements hidden with aria-hidden="true".
ng-state route links consume $aria defaults. Non-anchor and non-button route
links receive role="link", tabindex="0", and Enter/Space keyboard activation
when enabled. Links using data-state-active or data-state-exact receive
framework-managed aria-current while current unless an authored aria-current
value already exists.
Executable examples
<!doctype html>
<html>
<head>
<script type="module">
import "../../utils/source-bootstrap.js";
import "./ng-click-non-native.js";
</script>
<style>
.interactive-card {
border: 1px solid #cbd5e1;
border-radius: 6px;
padding: 12px;
width: fit-content;
cursor: pointer;
}
</style>
</head>
<body ng-app="ariaClickNonNativeDemo">
<section ng-controller="AriaClickNonNativeCtrl as vm">
<h2>Non-native ng-click accessibility</h2>
<p>Count: {{ vm.count }}</p>
<div class="interactive-card" ng-click="vm.count = vm.count + 1">
Click or press Enter/Space
</div>
<p>
AngularTS applies framework defaults (`role="button"` and
`tabindex="0"`), so this non-button element is keyboard accessible without
manual ARIA wiring.
</p>
</section>
</body>
</html>
Non-native ng-click accessibility
Count: {{ vm.count }}
AngularTS applies framework defaults (`role="button"` and `tabindex="0"`), so this non-button element is keyboard accessible without manual ARIA wiring.
<!doctype html>
<html>
<head>
<script type="module">
import "../../utils/source-bootstrap.js";
import "./form-validation.js";
</script>
<style>
.field {
margin-bottom: 12px;
}
.error {
color: #b91c1c;
}
input.ng-invalid.ng-touched {
border-color: #b91c1c;
}
</style>
</head>
<body ng-app="ariaFormValidationDemo">
<section ng-controller="AriaFormValidationCtrl as vm">
<form name="signupForm" novalidate>
<div class="field">
<label>Email</label>
<input
name="email"
type="email"
ng-model="vm.user.email"
required
aria-describedby="email-help"
/>
<p id="email-help">Enter a valid email to continue.</p>
</div>
<div class="field">
<label>Password</label>
<input
name="password"
type="password"
ng-model="vm.user.password"
required
minlength="8"
/>
</div>
<div class="field">
<button type="button" ng-disabled="signupForm.invalid">Create account</button>
</div>
</form>
<p class="error">
Form invalid: {{ signupForm.invalid }}
<br />
Email invalid: {{ signupForm.email.invalid }}
<br />
Password invalid: {{ signupForm.password.invalid }}
</p>
<p>
Notice how `aria-required` and `aria-invalid` are injected by `$aria` when
form inputs are in their default states.
</p>
</section>
</body>
</html>
Form invalid: {{ signupForm.invalid }}
Email invalid: {{ signupForm.email.invalid }}
Password invalid: {{ signupForm.password.invalid }}
Notice how `aria-required` and `aria-invalid` are injected by `$aria` when form inputs are in their default states.
<!doctype html>
<html>
<head>
<script type="module">
import "../../utils/source-bootstrap.js";
import "./ng-messages-live.js";
</script>
<style>
.field {
margin-bottom: 12px;
}
.error-box {
margin-top: 8px;
padding: 8px 10px;
border: 1px solid #fecaca;
background: #fff1f2;
border-radius: 4px;
max-width: 360px;
}
</style>
</head>
<body ng-app="ariaMessagesDemo">
<section ng-controller="AriaMessagesCtrl as vm">
<form name="contactForm" novalidate>
<div class="field">
<label>Email</label>
<input
name="email"
type="email"
ng-model="vm.user.email"
required
aria-describedby="email-hint"
/>
<p id="email-hint">Use an address we can message.</p>
<div
class="error-box"
ng-messages="contactForm.email.error"
ng-show="contactForm.email.dirty || contactForm.submitted"
>
<p ng-message="required">Email is required.</p>
<p ng-message="email">Please provide a valid email.</p>
</div>
</div>
<button type="submit">Submit</button>
</form>
</section>
</body>
</html>
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.