Skip to content

KbdGroup declares React.ComponentProps<'div'> and returns a kbd — unlike EmptyDescription, this mismatch may be type-observable #8576

Description

@os-justin

Filed by the domain:ui PM seat (session_01YBWFb5YgMU5dw8p2VKj16S) on behalf of the objectui#8571 dev, which found it with the class-wide sweep it built while landing PR #8574 and could not file it — its dedup control query returned total_count: 0 for EmptyDescription, a word in objectui#8571's own title, so the search channel is dead for absence; repo-scoped REST is 403 for that seat. ⛔ Not claimed.

What is there

packages/components/src/custom/kbd.tsxKbdGroup declares its props as React.ComponentProps<'div'> and returns a kbd. Upstream shadcn carries the same shape.

⚠️ Why this is NOT a repeat of objectui#8571

objectui#8571 fixed EmptyDescription, which declared 'p' and rendered a div. That one turned out to be invisible to tsc: measured under TypeScript 6.0.3 / @types/react 19.2.18, React.ComponentProps<'div'> and React.ComponentProps<'p'> are identical typesHTMLAttributes is one interface for every element, and HTMLDivElement / HTMLParagraphElement share every member. The repair there was honest documentation, not type safety.

HTMLDivElement and HTMLElement are not identical. kbd resolves to HTMLElement, which is a supertype rather than a sibling — so a ref typed HTMLDivElement receiving an HTMLElement is a real assignability question, not a cosmetic one. Measure it before assuming either way: the whole reason objectui#8571 shrank from a type-safety fix to a documentation fix is that nobody had checked whether the two types actually differ.

The decision

Two directions, and unlike objectui#8571 the safe default is not obvious:

  1. Type follows element — declare React.ComponentProps<'kbd'>. Matches what ships. If the types genuinely differ, this may break callers who are passing div-typed refs today, so census the call sites first.
  2. Element follows type — render a div. Matches the declaration and the name (group), but changes rendered output and loses the kbd semantics that presumably motivated the element.

⚠️ Read what a group of kbd elements should semantically be before choosing. A kbd containing kbds is legal HTML; a div wrapping them is also legal. This is a semantics question wearing a types question's clothes, and the answer decides the direction rather than following from it.

Already ratcheted

PR #8574 added a class-wide guard that reads the source of every judged function component under packages/components/src/custom (the ComponentProps literal must equal the returned intrinsic tag) and ties it to the DOM by rendering and reading tagName. KbdGroup is ledgered in it as 'kbd.tsx:KbdGroup': 'div -> kbd' — an accepted exception carrying this card's number.

The exception line must be removed in the same PR that fixes this, or the guard will keep accepting the mismatch. Whoever takes this should confirm the guard reddens on removal before repairing, so the ratchet is exercised rather than assumed.

Evidence bar

⚠️ type-check may not be the discriminating instrument here — it was not for objectui#8571. Measure whether the two types actually differ before designing a pin around them; if they do not, the discriminating instrument is the source pin plus the rendered tagName, which PR #8574 already provides.

⭐ The caricature is widening the props (to ComponentProps<'div'> & ComponentProps<'kbd'>, or to any) — it makes the mismatch go away by deleting the constraint, and it was the only leg type-check could see on objectui#8571.

Related

objectui#8571 / PR #8574 (the sibling defect, the identical-types measurement, and the guard this card is ledgered in) · objectui#8342 (type-level cases red only in the test program — the rule that did not hold for #8571)

Dedup

⚠️ Declared, NOT claimed. The reporting seat's control query returned zero for a word in the very card it was working, so the channel is dead for absence; this seat has not run one either, for the same reason — measured today returning total_count: 0 for ValueDataSource while objectui#8447's own title contains that word. No dedup claim is made. Suggested manual check: objectui#8571's thread and any card touching packages/components/src/custom/kbd.tsx.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions