Accessibility
A Jx document is a tree, and a good deal of what makes a page accessible can be read off the tree before it is ever rendered: whether a button has a name, whether an image has alt text, whether a tab sits in a tablist. Jx checks those facts wherever a document is judged, and every finding names the WCAG 2.2 success criterion it stands on, so a finding reads as a citation you can look up rather than an opinion.
Where the findings appear
Studio files them under the Accessibility source in Problems , on request through Check Accessibility in the command palette. Studio adds the checks that need the whole page: the heading outline, duplicate ids, the document language, vague link text, autocomplete purposes and autoplaying media. It also says what it could not check, such as colour contrast, rather than passing it.
jx validateprints the same findings beside its verdict. They are advisory by default, because a well-formed project is still valid; pass--strictto fail the run on an accessibility or overlay error.The UI kit, Studio's own chrome and every starter run the rules in their test suites, so a defect cannot ship in a template you scaffold from.
The rules
Each rule stays silent when the fact it needs is bound rather than written. A
template or a
$ref
in a name, a role or an id is a value the document decides while it runs, and a rule that accused you of a defect it could not see would teach you to ignore it.
| Rule | What it catches | Criterion |
|---|---|---|
interactive-unnamed |
A button, a link, an image-map
area
that links, a form control or an element with an interactive role that has no accessible name. |
4.1.2 Name, Role, Value |
img-alt-missing |
An
img
with no
alt
at all. An empty
alt
marks a decorative image and passes. |
1.1.1 Non-text Content |
aria-target-missing |
An
aria-labelledby
,
aria-controls
or similar reference to an id that nothing in the document has. |
1.3.1 Info and Relationships |
tab-outside-tablist |
A
tab
role with no
tablist
ancestor and no
tablist
owner. |
1.3.1 Info and Relationships |
menuitem-outside-menu |
A menu item role with no
menu
or
menubar
ancestor or owner. |
1.3.1 Info and Relationships |
option-outside-listbox |
An
option
role outside a
listbox
, a
select
or a
datalist
, and unowned. |
1.3.1 Info and Relationships |
tablist-none-selected |
A tablist whose tabs are none selected. A warning. | 4.1.2 Name, Role, Value |
dialog-unnamed |
A
dialog
with no
aria-label
or
aria-labelledby
. Its content does not name it. |
4.1.2 Name, Role, Value |
activedescendant-not-focusable |
aria-activedescendant
on an element that cannot take focus itself. |
2.1.1 Keyboard |
custom-element-in-select |
A custom element inside a
select
. Native
option
,
optgroup
,
hr
and
slot
are the only things that belong there. |
4.1.2 Name, Role, Value |
Naming a control
An accessible name comes from the first of these the browser finds:
aria-labelledby
,
aria-label
, a native label, the control's own content for roles that allow it, and
title
. In a Jx document that means any of the following names a button:
{ "tagName": "button", "textContent": "Save" }
{
"tagName": "button",
"attributes": { "aria-label": "Close" },
"children": [{ "tagName": "jx-icon", "$props": { "name": "x" } }]
}
A text field is named by a
label
whose
for
matches its
id
, by a
label
wrapped around it, by
aria-label
, or as a last resort by its
placeholder
. A submit button is named by its
value
. A
slot
counts as content, because the text it will carry is the component's caller's to write.
Inside a component
The three container rules do not judge the root of a component definition, and they do not judge anything under a custom element on a page. A
role="tab"
row you slot into a tabs component is checked inside that component's own definition, where its
tablist
lives, not against the page that uses it.
custom-element-in-select
works the other way round, and it is the one rule a custom element switches on rather than off. A
select
builds its own rows, so an element it does not know is not in
select.options
: it cannot be picked, the arrow keys never reach it, and no
change
event fires. It still renders, and with
role="option"
on it the accessibility tree reads as though the control worked, which is exactly what makes it worth a rule. If you need a list of rows you can style, build a listbox out of
jx-listbox
and
jx-option
; if you need a form control, every row of a
select
is a native
option
.
They also accept a container that claims the element through
aria-owns
. ARIA states containment where the tree cannot, so a tab a
tablist
names in
aria-owns
is that tablist's child in the accessibility tree wherever you write it. The owner's role is what counts: a
role="group"
naming a tab in
aria-owns
owns nothing a tab may belong to, so that tab is still reported. A bound
aria-owns
, or a bound id on the element, silences the check the way a bound ancestor role does.
What is not checked
Colour contrast, target size, focus order and reading order depend on the rendered page and the cascade, and a tree cannot answer them. Studio's report lists each one it could not check so that an empty list never reads as a clean bill. Run the built site through a browser-based checker for those.