skip to content

In a shared component library, a driver-app team used a button's polymorphic render-target property to render it as a generic container; what broke, and how should the API prevent it?

level: seniorimportance: should knowfreq 38%

answer

  1. looks right, tapping works
  2. no role, no focus, no keys
  3. the target decides the semantics
  4. a typed list of targets
  5. properties valid per target

basics

~20 s

The styled container kept the button's look and pointer tap but lost its role, focusability and keyboard activation, so assistive-technology and keyboard users could not accept trips. The API should restrict render targets to semantically compatible ones and type the properties each allows.

solid answer

~50 s

A **polymorphic render target** lets a caller choose which element the component renders as — usually so a button-styled control can be a real link. Rendered as a generic container, the 'Accept trip' action still looked like a button and a pointer tap still worked, so visual review passed. But it had no button role, was not focusable, and did not activate on Space or Enter, which the WAI-ARIA Authoring Practices button pattern expects; drivers using a screen reader, switch access or an external keyboard could not accept trips. Button-only properties such as disabled were also silently ignored. The library should **constrain targets** to a typed set that keeps interactive semantics — a native button, a link, the app's navigation link — **type the properties valid for each**, and warn in development when an unsupported target is used.

go deeper

for a junior

Know that a polymorphic render target changes the element underneath, and that the element, not the styling, decides whether it acts like a button.

for a middle

Explain why a link and a button are the usual valid targets, and what the button pattern expects: focus, a button role, activation on Space and Enter.

for a senior

Diagnose a failure that passes visual review, trace it to an unconstrained API, and prescribe typed targets, per-target properties, warnings and a sanctioned layout option.

for a principal

Decide whether the library offers polymorphism at all, weighing type complexity and contract growth against the forks teams create when they cannot render a styled control as a link.

## What a polymorphic render target is A **polymorphic render target** is a property that lets the caller decide which underlying element a styled component renders as. The classic use is a button-styled control that must sometimes *navigate* — to a trip's details screen, say — and therefore be a real link rather than a button. The library keeps one component and one set of styles, and the caller picks the element. It is a useful feature and a sharp one: the component's **look** comes from the library, but its **semantics** — role, focusability, keyboard behaviour — come from whatever element the caller picked. ## What broke in the driver app The team wanted an "Accept trip" action laid out as a large tile, and chose a generic container as the render target to escape some default spacing. The result: | What still worked | What silently broke | |---|---| | the button's visual style | no button role, so a screen reader announced plain text | | a pointer or finger tap | not in the focus order, so keyboard and switch users skipped it | | visual review and screenshots | no activation on Space or Enter | | | the disabled property was ignored, so the tile stayed tappable while a trip was loading | The WAI-ARIA Authoring Practices button pattern says that when a button has focus, **Space** and **Enter** both activate it. A generic container gives none of that. Drivers who use a screen reader, switch access, or an external keyboard in a vehicle mount could not accept trips at all — a failure no screenshot test would catch. The root cause is not the team's choice; it is an API that **accepted any element** and let semantics fall away without a warning. ## How the API should prevent it 1. **A closed, typed set of targets.** Allow only elements that keep interactive semantics: a native button, a link, and the app's navigation-link component. Anything else fails type checking. 2. **Properties typed per target.** When the target is a link, require a destination and reject button-only properties such as disabled; when it is a button, reject a destination. The API then describes what each target really supports. 3. **Development-time warnings** for any runtime path the types cannot see, such as a target chosen from data. 4. **A layout escape that does not touch semantics.** The team reached for a container because they needed layout control. A sanctioned layout option — full width, a tile size — removes the temptation. 5. **Examples in the component workshop** for each supported target, so the supported uses are visible. Adding a button role to the container would not have been enough on its own: the Authoring Practices note, for the link role for instance, that a role does not bring the element's standard behaviour, so focusability and key handling would still be the author's job. Re-implementing a native control's behaviour inside the library is almost always worse than restricting the target. ## The trade-offs of polymorphism itself - **Type complexity.** Deriving the valid properties from the chosen target is hard to type well and can slow the type checker in large apps. - **Documentation surface.** Every supported target multiplies the examples, the tests and the edge cases. - **Contract growth.** Each target the library accepts becomes a promise; dropping one later breaks the callers who used it. An alternative some libraries prefer is **merging onto a caller-supplied element**: the caller renders its own link or button and the library merges its styles, handlers and accessibility attributes onto it. That moves the responsibility for choosing a sensible element to the caller, so it still needs the same guardrails — typed expectations and development warnings. ## The native mobile parallel Native component libraries meet the same trade-off when a styled control can be wrapped around arbitrary content. The rule transfers directly: the look may be reusable, but the control's traits — that it is a button, that it can be focused and activated — must come from an element that really provides them.

  • In a component library, how should a polymorphic button handle a render target chosen at runtime, for example from server-driven layout data?
    Types cannot check a value that arrives at runtime, so the API should accept a small named set of targets rather than arbitrary elements, map each name to a known-safe element, and fall back to a native button with a development warning for anything unknown. That keeps server-driven screens from reintroducing the same silent semantics failure.
  • What is the trade-off between a polymorphic render-target property and merging the library's behaviour onto a caller-supplied element?
    A render-target property keeps the choice inside the library's API, so it can be typed and constrained, but its types grow complex. Merging onto a caller-supplied element is flexible and composes with the app's own components, but the caller now picks the element, so semantics depend on caller discipline plus development warnings.

saying these in an interview costs you the question

  • If it looks like a button and taps work, the render target does not matter
  • Adding a button role to a container gives it full button behaviour
  • A polymorphic property should accept any element for maximum flexibility
  • Keyboard activation is irrelevant in a touch-first mobile app
  • Disabled works the same whatever element the component renders as