Skip to content

fix(button): stabilize labels + prevent ID collisions - #936

Open
caugner wants to merge 1 commit into
mainfrom
913-replace-randomIdString
Open

caugner wants to merge 1 commit into
mainfrom
913-replace-randomIdString

Conversation

@caugner

@caugner caugner commented Oct 17, 2025 •

Copy link
Copy Markdown
Contributor

Description

Stabilize button label rendering: use aria-label for string labels and a fixed labelId for slotted labels inside each mdn-button shadow root.

Motivation

Reduce SSR output differences while keeping label references valid.

Additional details

Following Leo's feedback, retain random IDs as a fallback for template labels without an explicit ID. Add SSR coverage for string, template, and slotted labels. Playground template markers remain tracked separately in the follow-up note.

After this, the only build differences are caused by Lit markers on Curriculum pages, and on the Playground.

Related issues and pull requests

Fixes #913.

@github-actions

github-actions Bot commented Oct 17, 2025 •

Copy link
Copy Markdown
Contributor

0b81566 was deployed to: https://fred-pr936.review.mdn.allizom.net/

@caugner caugner changed the title fix(utils): replace randomIdString() with deterministicIdString() fix(utils): replace randomIdString() with deterministic createElementId() Apr 13, 2026
@caugner
caugner marked this pull request as ready for review April 13, 2026 15:03
@caugner
caugner requested a review from a team as a code owner April 13, 2026 15:03
@caugner
caugner requested a review from LeoMcA April 13, 2026 15:03

@LeoMcA LeoMcA left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this fixes everything in the wrong place:

We're sacrificing the guarantee of ID-uniqueness in a number of places, which would result in invalid HTML, and a confused accessibility tree, for an issue which IIRC we already have a fix for: just upload everything on each build, no rsync/diff - that's the slow bit.

Regardless, having deterministic output is a good goal, but I don't think this is at all the correct approach: the new function does very little that manually adding ids would do, and the addition of name in dropdowns isn't enforced, nor is necessary - we can just add ids to the slotted elements directly in the DOM.

I'd suggest we keep the random ID fallback where we can't be sure that manual IDs have been set, and enforce this deterministic requirement in CI so we catch future fallbacks to the random ID and fix.

Comment thread components/dropdown/element.js Outdated
Comment thread components/utils/index.js Outdated
Comment thread components/utils/index.js Outdated
Comment thread components/dropdown/element.js Outdated
Comment thread components/button/pure.js Outdated
@caugner
caugner marked this pull request as draft April 23, 2026 14:06
@caugner caugner changed the title fix(utils): replace randomIdString() with deterministic createElementId() fix(components): stabilize button + example IDs Sep 17, 2026
@caugner

caugner commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up issue draft for the remaining Playground non-determinism:

Title: lit$…$ markers make Playground HTML non-deterministic across builds

After #936, the only remaining build-to-build difference in the SSR output is in /play/ pages, where attribute bindings on mdn-play-runner and mdn-modal carry lit-html's template marker (e.g. lit$391985138$0 vs lit$070035930$0). lit-html seeds that marker with Math.random() at module load, so every SSR process produces different attribute values.

Expected: identical HTML on repeated builds of unchanged content, so the deployment sync skips these files.

Possible approaches: find out why the marker leaks into the rendered output on these pages (it should be consumed during template preparation), or strip it in post-processing. Independently, add a CI job that runs the SSR step twice and fails on any diff, to catch future regressions.

Related to #913.

@caugner caugner changed the title fix(components): stabilize button + example IDs fix(button): replace random aria-labelledby ids with aria-label + labelId Sep 17, 2026
@caugner
caugner force-pushed the 913-replace-randomIdString branch 2 times, most recently from 10adf28 to 0a1dee0 Compare September 17, 2026 18:27
@caugner caugner changed the title fix(button): replace random aria-labelledby ids with aria-label + labelId fix(button): stabilize labels + prevent ID collisions Sep 23, 2026
@caugner
caugner force-pushed the 913-replace-randomIdString branch from 0a1dee0 to 47801d4 Compare September 23, 2026 12:52
@caugner
caugner marked this pull request as ready for review September 23, 2026 13:58
@caugner
caugner requested a review from LeoMcA September 23, 2026 13:58
@caugner

caugner commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

FYI @LeoMcA The scope of this PR has changed following your review.

Comment thread components/button/pure.js
Comment on lines +39 to +41
const ariaLabel = typeof label === "string" ? label : undefined;
labelId =
ariaLabel === undefined ? (labelId ?? randomIdString("label-")) : undefined;

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.

aria-label is now emitted for every string label, including buttons whose label is visible. components/homepage-contributor-spotlight/server.js:35 renders Button({ label: context.l10n(...)\Get involved`, icon: arrowRightIcon })—iconOnlyis unset, so the output is…Get involved. Duplicating visible text into aria-labeloverrides the name computed from content and is handled differently by page translation and voice-control tooling, which risks a Label in Name (WCAG 2.5.3) mismatch that the oldaria-labelledby` → visible span could not produce.

Restrict aria-label to the iconOnly case, where the label span is hidden and an explicit name is actually required. Otherwise the accessible name already comes from the visible span (the icon SVGs carry no <title>, so they contribute nothing).

Suggested change
const ariaLabel = typeof label === "string" ? label : undefined;
labelId =
ariaLabel === undefined ? (labelId ?? randomIdString("label-")) : undefined;
const ariaLabel = iconOnly && typeof label === "string" ? label : undefined;
labelId =
typeof label === "string"
? undefined
: (labelId ?? randomIdString("label-"));

AI-generated review by Claude

This branch was previously deployed

1 inactive (outdated) deployment
review — 411203c0 Deployed Oct 17, 2025 by caugner via deploy / deploy #1427
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.

Use of randomIdString() impacts deployment time

3 participants