ADR 0176: A select is two shapes, and the contract says which
Status: Accepted
Context
Section titled “Context”A select renders the platform’s own <select> unless it filters, and the combobox when it does. The
catalogue said so in prose — in a comment explaining why arrow and placeholder are not in the
kind’s required list — and said nothing an instrument could read.
Read as one anatomy, the contract owed every select the combobox’s parts and its opener relation, so a sweep across the three renderers reported six divergences that were not divergences:
select aria-activedescendant · aria-controls · aria-expanded · aria-haspopup · aria-selectedselect the value element, the placeholder, the arrowAll of them say the same thing: one renderer draws a combobox and two hand the list to the platform.
Repairing any of them would mean giving a native <select> attributes it must not have — a <select>
claiming aria-expanded is lying about what it is — so they sat unfixable, and select.value was
carried as an open row for weeks.
The type that names variants had reserved this exact question: “A second kind varying on something else widens this union — and that is the moment to ask whether the two axes belong in one type.”
Decision
Section titled “Decision”The two shapes are variants, and the catalogue declares both.
custom the combobox: a trigger holding the value, a mark that says it opens, a placeholder in place of the value, an overlay for the opener to point atnative the platform's chooser: the trigger is the <select>, the placeholder is an <option>, the arrow is the platform's, and nothing carries the combobox relationcustom requires arrow and nothing else. That is not an oversight: a variant’s required says
must be there, which overrides a presence condition rather than joining it, so listing value
would ask a custom select showing its placeholder for a value element it correctly does not draw.
value and placeholder keep their own conditions and simply do not exist in the native shape.
native describes what the platform makes of the two parts it does have — trigger is a listbox
element, placeholder is an option — because a shape the contract does not describe is one every
check reports as broken.
Two axes now share one variant vocabulary, which is the question the type reserved. They stay in
one type because a variant name is only meaningful for the kind that declares it: asking a select
about multi selects no anatomy rather than the wrong one. That property is what makes the shared
vocabulary safe and is the thing to keep if a third axis arrives — the union may grow, and a lookup
must stay a lookup.
The conformance kit learns that a <select> is a combobox (and a <select multiple> a listbox),
so the native shape carries the role its trigger promises without spelling it — the same rule that
already applies to <input type="number"> and its spinbutton.
Consequences
Section titled “Consequences”A checker must now say which shape it is looking at. That is the cost, and it is the point: measured against its own variant each renderer is clean, and measured against the other each fails —
lit, not searchable as native clean as custom the placeholder is an <option>lit, searchable as custom clean as native the placeholder is not an <option>— which is what a variant is for, and what “one kind, two anatomies” could not say before.
The opener relation belongs to the custom shape, and MDY_POPUP_OPENERS.select says so — a
variant on the relation, absent everywhere else because every other kind has one shape.
Which shape a document asks for is published, which is the half this record first shipped without
and the peer’s measurement made unavoidable: the catalogue named two shapes and nothing at runtime
said which one a given field selects, so a renderer drawing one and ignoring the property violated
nothing stated. variantOf(kind, spec) answers from the document’s own words — a multiselect’s
mode, a select’s searchable — so a renderer and a checker read one rule.
That makes a real divergence visible rather than arguable: Plain draws the combobox for every select, including one a document did not ask to filter, where the other two hand it to the platform. Stated this way it is a renderer that implements one of two declared shapes, which is a finding somebody can act on; before, it was three renderers disagreeing about six attributes with nothing to appeal to.
Alternatives rejected
Section titled “Alternatives rejected”Gate the parts on a capability, as the reorder grip is gated. Tried first. It needs a capability
word — searchable — named in one table and nowhere else, which is a second vocabulary for something
the catalogue already expresses. A capability says whether the question applies; this is two
anatomies, which is what a variant means.
Make the renderers agree. Two of them would have to draw a combobox where the platform’s chooser is better — its typeahead, its mobile picker — or one would have to abandon the combobox. The difference is deliberate and the contract’s own prose says so.
Leave it in prose. It had been, for as long as the comment existed, and it produced six findings nobody could act on.
Verification
Section titled “Verification”npm run test:conformance runs each renderer against the contract; the variant is the caller’s to
state. Measured while writing this: each shape conforms to its own variant and fails against the
other, in both directions, which is the check that the two are actually distinguishable.
contract:diff classified the two declarations as minor and they are accepted in the snapshot.
Each renderer’s conformance config now declares the shapes it draws and the kit mounts one run
per shape: lit and Angular declare both, Plain declares the combobox, which is the only one it makes.
Planting a fixture that ignores the variant — mounting the combobox for the native run — turns that
run red, so the declaration is exercised rather than merely written.
Not verified: that Plain’s single shape is the right answer. This record makes it measurable and does not settle it — a renderer may legitimately implement one shape, and whether this one should draw the platform’s chooser when a document does not ask to filter is a decision about that renderer.
Security and privacy
Section titled “Security and privacy”No impact. Anatomy and naming only.