Repository navigation
fix(components): the ui:select label names its combobox through htmlFor and the trigger id (objectui#10435) - #10548
Conversation
…or and the trigger id The `select` form renderer drew its Label with no htmlFor and the Radix SelectTrigger with no id, so a labelled select's combobox had an empty accessible name. The trigger now gets schema.id and the label htmlFor to it, the element:record_picker shape (objectui#5771). A select without an id mints none, as on the sibling input and textarea renderers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Reviewed against the merge-base ① Derived judgmentsBehaviour changes the diff introduces, judged against the card, the triage comment and the ruled shape.
Asked and delivered.
Done but not asked.
Existing pins edited or deleted.
Can the new pins fail? (
② Semver level
Changeset prose, sentence by sentence, against the diff:
Code-comment prose added by the diff ( Test-file prose: the header restates the card (TRUE as above); "the no- No ③ Boundary flagsThe PR body's own flags and open questions.
CI at the head
Is the PR draft? Yes ( Does Did main move under the PR's files since the merge-base? No. Not measured by this review: the Chromium accessibility tree after the fix (the card's measurement was pre-fix; no browser harness exists in this package, and the PR says so); the local vitest / eslint / gate runs the PR body reports (the author's claims, superseded by CI at the head). Implemented-by: VERDICT: PASS |
Fixes #10435
Clause-②: no
What changed
ui:select(packages/components/src/renderers/form/select.tsx) now writeshtmlFor={schema.id}on itsLabelandid={schema.id}on the RadixSelectTrigger. This is therecord-picker-label-association-5771shape the triage ruled. Before, neither end carried the wiring. The trigger is abuttonwithrole="combobox", a role that takes no name from its content, so a labelled select's combobox had an empty accessible name.idis found bygetByRole('combobox', { name: 'Title' }).Title: the objectui#10368 marker span isaria-hidden.idmints none. Its caption stays unassociated, as on the siblinginput/textarearenderers andelement:record_picker. This half is pinned as deliberate. See Acceptance notes for the dispatch hypothesis it contradicts.Files (vs base
111fa4c)packages/components/src/renderers/form/select.tsxpackages/components/src/renderers/form/__tests__/select-label-association.test.tsx(new, 7 cases)packages/components/src/__tests__/required-marker-markup.test.tsx.changeset/10435-select-label-association.md(@object-ui/componentspatch)required-marker-markup.test.tsxis outside the claim's declared file surface. Its select site carried the comment "This label carries nofortoday", which this change made false. The comment is rewritten, and the locator becomeslabel[for="sel-ctl"], the same form as the other five sites.Verification, on final head
4e99a54(merge oforigin/maind461b0d)pnpm exec vitest run packages/components/src/renderers/form/ packages/components/src/__tests__/required-marker-markup.test.tsx packages/components/src/__tests__/form-renderers.test.tsxgave exit 0, withTest Files 67 passed (67)andTests 494 passed | 10 skipped (504).pnpm turbo run type-check --filter @object-ui/components --concurrency=2gave exit 0 withTasks: 9 successful, 9 total. It ran ona75bf59. The merge after that only touchedpackages/app-shell,packages/plugin-dashboard,content/docsand two changesets, all outside@object-ui/componentsand its@object-ui/*dependencies.tsc -p tsconfig.test.json --listFilesOnlylists both touched test files.select.tsxalso shows 4.check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:new-line-citations(0 new),check:changeset-claims,check:pending-changeset-literals,check-changeset-presence,check-changeset-no-major,check-changeset-overwrite,check-changeset-fixed,check-lint-coverage.check:sdui-registration-pinsexits 2 with a prerequisite refusal, because it needs a console build. This diff does not change the registration block. That gate is declared to CI.Reverse verification (ablation)
The fix was committed first as
a75bf59. The script then overwroteselect.tsxwith the BASE blob4296933c:htmlFor={schema.id}count went 1 to 0 on disk andSelectTrigger id={schema.id}count went 1 to 0. It ran the pin andrequired-marker-markup.test.tsx, then restored withgit checkout HEAD --under anEXIT INT TERMtrap.Tests 8 failed | 14 passed (22).Unable to find an accessible element with the role "combobox" and name "Title".idcase stayed green, as expected.required-marker-markupfailed, because of the tightenedlabel[for]locator.8fc729cbequals the HEAD blob, andgit diff HEADis empty.Acceptance notes
schema.idstill needs a minted id, for example fromuseId. The ruled 5771 shape explicitly declines auseId()fallback, and its own test pins the no-idcase as unassociated. The ruling was followed. The alternative is raised to the dispatching seat as an open question, not decided here.components-form-select/basic-selectandcomponents-form-input/with-labelauthor alabelwith noid. They render an unnamed control under the ruled shape. This is noted, not filed.Implementation session:
https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy(dispatched by thedomain:uiseat 5).Generated by Claude Code