Skip to content

[a11y] The RLS USING/CHECK predicate editors render a label bound to nothing — CelPredicateField's id is optional and three call sites omit it #4533

Description

@yinlianghui

What was measured

CelPredicateField accepts an optional id prop and uses it for BOTH halves of the association:

Label className="…" htmlFor={id}      ← packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx:311
Textarea ref={taRef} id={id}          ← same file, :316

When a call site omits id, both become undefined, so the visible label is bound to no control: the textarea has no accessible name, and the label is orphaned text.

Three of the four call-site groups omit it:

call site passes id?
inspectors/ObjectFieldInspector.tsx :478 / :615 / :628 / (formula) yes — id={"field-rule-visible-…"} etc.
PermissionAdvancedFacets.tsx :356 (RLS USING), :372 (RLS CHECK) no
ConditionalFormattingEditor.tsx :272 no

So the RLS policy editor's USING and CHECK predicate fields — the row-filter authoring surface of a permission set — and the conditional-formatting rule editor render a label that reads correctly on screen and resolves to nothing for a screen reader. ObjectFieldInspector shows the intended shape: the prop exists and is used correctly there.

How it surfaced

Writing the wire pins for objectui#4302 I reached for the obvious selector and testing-library refused it verbatim:

TestingLibraryElementError: Found a label with the text of: USING (read filter),
however no form control was found associated to that label. Make sure you're using
the "for" attribute or "aria-labelledby" attribute correctly.

The test now selects that textarea by placeholder instead, with a comment pointing here. That is a workaround in a test, not a fix.

Why this is filed rather than fixed

Out of scope for objectui#4302, whose ruling limits the surface to the package-door slice merge and its pins. Same family as the already-fixed objectui#3994 (metadata-admin inspectors' shared field components had Label next to the control without associating them) and objectui#3341 — this is the residue those two did not reach.

Suggested shape

Pass an id at the three call sites (the RLS facet has a policy index available: rls-{i}-using / rls-{i}-check), or have CelPredicateField fall back to a generated id (React.useId()) when the prop is absent, so a call site cannot silently produce an unlabelled control. The second is the "hard to get wrong" direction — the label is rendered by the component itself, so the component can guarantee the association rather than trusting every caller to remember.

Unassigned, for triage.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions