a11y/disallowed-aria-props · ARIA attribute not allowed on this role
An aria-* attribute the element's role does not support is ignored; one the role prohibits, such as a name on a bare div, is a name that is not exposed.
Severity: warning · Category: a11y
What it checks
Flags an aria-* attribute that the element’s role either prohibits or does not own, judged against the ARIA 1.3 role tables in component source.
Which role is judged:
- An explicit
role, taking its first concrete token, as a browser resolvesrole="switch checkbox". A role ARIA does not define isa11y/invalid-role’s finding, not this one; a DPUB-ARIA role (doc-toc, …) gets no judgment, since the role tables this rule reads do not cover DPUB. - Otherwise the element’s implicit role, and for the elements whose implicit role depends on context (
<a>islinkonly withhref,<img alt="">ispresentation,<input>is whatever itstypesays), every role the element could have. A judgment is made only when it holds under all of them. So<div aria-label>fires (a<div>isgenericeverywhere, andgenericdoes not take a name), while<a aria-label>,<img aria-label>and<input aria-checked>do not.<input>is effectively unjudgeable for any non-global attribute here; the Svelte compiler’sa11y_role_supports_aria_props_implicitcovers<input type="text" aria-checked>. - An expression role, or a spread with no literal role, leaves the role unknowable: no finding.
Two kinds of finding, with different messages:
- Prohibited.
aria-label,aria-labelledbyoraria-braillelabelon an element whose role does not take a name (<div>,<span>,<p>,<code>,<label>,<time>, …), or any attribute a role’s table lists as prohibited (aria-roledescriptionongeneric). Message: “aria-labelis prohibited on<div>— its role does not take a name”. - Not supported. An attribute absent from the role’s table:
aria-checkedonrole="button",aria-levelon a<span>. Message: “aria-levelis not supported by rolegeneric”.
<div aria-label="Breadcrumb">Home / Gallery</div>
<div role="button" tabindex="0" aria-checked="true">Toggle</div>
Not flagged:
- An attribute
a11y/unknown-aria-attributealready reports: one typo, one finding. - The (role, attribute) pairs the ARIA 1.3 tables no longer list but the Svelte compiler’s data still accepts (
aria-levelonlistitem,aria-expandedonlistbox, …). Where the compiler and this rule would disagree on the same markup, the compiler wins. <address aria-label>and<hgroup aria-label>: the dataset marks both as not taking a name, but the ARIA-in-HTML specification and axe give bothrole=group, which does. This rule follows the specification.- Anything a value could fix (
a11y/invalid-aria-value) or a missing required attribute (a11y/required-aria-props).
Overlap with the Svelte compiler. For explicit roles and the implicit roles the compiler maps, a11y_role_supports_aria_props / _implicit report the not supported case too, and this rule agrees with it. The compiler is silent on the prohibited case: <div aria-label> compiles without a warning, and that is the case that appears in real code.
Where axe differs. axe’s aria-prohibited-attr grades a prohibited name as needs review when the element has text content and serious only when it does not, and exempts a name whose nearest ancestor role is a widget. This rule reports all of them: ARIA prohibits the attribute on the role regardless of what else names the element. <label> is the one to know about. The specification prohibits a name on it only when it is exposed as generic; the dataset and axe both prohibit it unconditionally, and <label for=… aria-label="close sidebar"> will fire.
Why it matters
An unsupported aria-* attribute is dropped by assistive technology, so the state the author meant to convey is not conveyed. A prohibited one is worse: aria-label on a bare <div> looks, in the source, like a labelled region, and screen readers announce nothing for it. The label exists only in the author’s mental model.
How to fix
Give the element a role that supports the attribute, or move the attribute to the element that owns the semantics:
<nav aria-label="Breadcrumb">Home / Gallery</nav>
<div role="switch" tabindex="0" aria-checked="true">Toggle</div>
Mode differences
None. This rule reads source, the same .svelte and .ts files, everywhere it runs. The CLI, the Vite plugin’s build pass, and the live dashboard’s static baseline all report it identically, and the rendered-HTML pass never re-evaluates it. Scoping a run with --route skips it: component-scoped rules have no route to attribute a finding to.
Disabling
Silence a single element with <!-- svelte-vitals-disable-next-line a11y/disallowed-aria-props -->, or turn the rule off:
export default {
rules: {
'a11y/disallowed-aria-props': 'off'
}
};