Skip to content

Flesh out setting the accesskit label and labelledby fields - #1223

Open
hovinen wants to merge 17 commits into
DioxusLabs:mainfrom
hovinen:support-html-label-for-accessible-name
Open

hovinen wants to merge 17 commits into
DioxusLabs:mainfrom
hovinen:support-html-label-for-accessible-name

Conversation

@hovinen

@hovinen hovinen commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

The existing implementation for setting the label and labelled_by fields in accesskit nodes focused on the aria-label and aria-labelledby attributes. This adds support for the <label> element as well as the alt attribute of the <img> tag. This fills out support for the host language label as defined in the W3C ARIA spec.

This adds suitable unit tests to ensure that the accesskit tree is correctly constructed.

There are still some gaps, such as support for the HTML global title attribute via accesskit's tooltip field, but this brings the implementation much closer.

One tradeoff for consideration: This implementation sets the label field in each accesskit Node based not just on the label, but the labelledby references as well. This means that all of the complicated logic surrounding how to put labels together is handled right when building the ARIA tree, and consumers can get the accessible name of an element by just calling label(). But this implies more memory use and slightly higher overhead than would be the case if the accesskit contract were minimally followed.

Note for review: The production code in this PR is largely superseded by #1346. So this is interesting mostly for the test coverage it provides and the general question about using the accesskit label to store the computed accessible name.

Here are some measurements on the CPU and memory impact:

Setup

  • Blitz branch support-html-label-for-accessible-name at commit 8fbaa6162.
  • Temporary patch: an environment variable SKIP_DERIVED_LABELS disabled two things:
    • the text computed for aria-labelledby and <label> targets
    • the name from content (subtree_text)
  • aria-label and host-language labels (alt, input value, figcaption, …) were still computed in both runs.
  • blitz-dom compiled at opt-level = 3 (--config profile.test.package.blitz-dom.opt-level=3). Other crates use the default test profile. Release flags were not used for the whole workspace.
  • Timing: Instant around 20 calls to build_accessibility_tree after one warm-up call, averaged per call. Four separate process runs for each configuration.

Test document (one synthetic page)

Content Count
<button>Button number N</button> 3,000
<a href="#">Link number N with some longer link text</a> 3,000
<h2>Heading number N describing a section</h2> 600
<div role="button" aria-labelledby="lN"> + <span id="lN"> 300
<label for="iN"> + <input id="iN"> 300
<div><p> of plain paragraph text 6,000

Resulting tree: 33,004 accessibility nodes, of which 13,200 are TextRun nodes.

Results

With derived labels Without derived labels
Nodes with a label 7,200 0
Total label text 210,750 B (205.8 KiB) 0
build_accessibility_tree, 4 runs 23.47, 24.11, 23.81, 24.52 ms 21.65, 21.68, 21.87, 22.03 ms
Mean 23.98 ms 21.81 ms
  • Time saved: about 2.2 ms per full tree build, roughly 9%.
  • Label text saved: about 206 KiB, plus 7,200 small Box<str> allocations whose overhead I did not measure.

For scale:

Item Size
size_of::<accesskit::Node>() 128 B
33,004 nodes × 128 B 4.03 MiB
TextRun value text (13,200 nodes) 544.6 KiB (557,640 B)
Derived label text 205.8 KiB

The label text is about 4% of the sum of these three, excluding allocator overhead and child lists, which I did not measure.

Caveats

  • One synthetic page. Real pages will have a different ratio of named elements to text.
  • Only blitz-dom was optimised, and timings are wall-clock on a laptop.
  • The tree is rebuilt in full on every update. accesskit_consumer keeps its copy while the window exists.

What accesskit_consumer 0.39.1 would derive without label (from reading the source, not measured)

  • labelled_by targets are read only from the target's label property, or value for a Role::Label. A generic target such as a span has no label and would contribute nothing.
  • Name from content is derived from descendants only for Button, CheckBox, DefaultButton, Link, MenuItem, MenuItemCheckBox, MenuItemRadio and RadioButton. Other roles (headings, cells, tabs, tree items) would have no name.
  • The consumer joins descendant pieces with a space each time, so inline markup such as Sa<b>ve</b> would give Sa ve.

WPT results

No changes in test results compared to main.

Generated by the WPT workflow.

}

#[test]
fn ignores_label_element_which_is_hidden() -> TestResult<()> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test and behavior is the inverse of what it should be. label is an example explicitly called out as something that shouldn't be ignored.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the spec makes my head hurt, but here goes:

If the current node is hidden and is:

  • Not ...irrelevant to this test...
  • Nor part of a native host language text alternative element (e.g. label in HTML) or attribute traversal, where the root of that traversal was hidden.

Return the empty string.

How one interprets this depends on how one should read "the root of that traversal". This test interprets that to be the button, which is not hidden. Which means that the current node (in this case, the label) is indeed not part of "a native host language text alternative where the root element of the traversal was hidden." The first criterion relates to attributes which don't appear in the code, so that clearly doesn't apply. Thus neither of the two criteria in the bullet points apply, so one reaches the conclusion "Return the empty string".

A test with Chromium indicates that it also follow this interpretation.

All very confusing, but I think this is correct.

Comment thread packages/blitz-dom/src/accessibility.rs Resolved
Comment thread packages/blitz-dom/src/accessibility.rs Resolved
Comment thread packages/blitz-dom/src/accessibility.rs Resolved
@hovinen
hovinen force-pushed the support-html-label-for-accessible-name branch from 8fbaa61 to e84a9f5 Compare October 30, 2026 12:22

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants