AddThisFeature

Headless Component Wrapper

Put the app's own styling and API in front of behaviour-only primitives.

involved Components & Primitives

What it adds

A wrapping layer that gives unstyled third-party primitives the app's visual language and its own stable interface.

What your agent is told to do

5
  1. 1

    Identify the behaviour-heavy primitives already in use — menus, dialogs, comboboxes, date pickers, tooltips — and give each one wrapper that the rest of the app imports instead of the primitive directly.

  2. 2

    Define the wrapper's interface from the app's own vocabulary of sizes, tones, and states, and translate to whatever the primitive expects inside. The call site should not need to know which primitive is underneath.

  3. 3

    Preserve every accessibility behaviour the primitive provides — focus trapping, roving focus, labelling, keyboard handling, dismissal — and verify it survives the wrapping rather than assuming it does.

  4. 4

    Keep the wrapper thin enough to be replaced. Confine the primitive's concepts to that one file so swapping it later is a rewrite of the wrapper, not an audit of every screen.

  5. 5

    Do not let some screens import the wrapper and others reach past it to the primitive. A bypass route means the styling, defaults, and accessibility fixes live in two places, and the second one drifts.

Edge cases it handles

7
  • Vendor props must not leak through the wrapper's interface. Spreading unknown props straight onto the primitive means every call site can quietly depend on vendor internals, and the wrapper stops being replaceable the moment one does.
  • The primitive's accessibility work is the reason it was chosen, and it is easy to undo by overriding a role, replacing an internal element, stripping a generated identifier, or intercepting a keyboard handler. Every wrapper needs its keyboard and screen reader behaviour checked after wrapping.
  • There must be a defined exit path from the vendor. If the primitive is abandoned or diverges, the work should be confined to the wrapper, which means no vendor type, prop name, or concept may appear in application code.
  • A parallel component that bypasses the wrapper defeats the whole arrangement. Remove the direct import route, or make it obvious in review, so a second unstyled dialog does not appear next to the styled one.
  • The primitive's own defaults may not match the app's conventions — dismissal on outside click, initial focus, portal target, animation timing — so the wrapper must set them explicitly rather than inheriting whatever the vendor decided.
  • Version upgrades of the primitive can change behaviour without changing the wrapper's interface, so the wrapper's behaviour needs verifying on upgrade rather than trusting the type check to pass.
  • Where a wrapped primitive exposes several coordinated parts, its composition should follow the same conventions as Compound Component Pattern rather than inventing a second way to compose the same shapes.

Definition of done

8
  • Every behaviour-only primitive in use is reached through an application wrapper.
  • Application code contains no vendor prop names, types, or concepts.
  • Keyboard navigation, focus management, labelling, and dismissal behave identically before and after wrapping.
  • The wrapper sets the app's own defaults rather than inheriting the primitive's.
  • There is no route by which a screen can use the primitive directly.
  • Replacing a primitive requires changing only its wrapper.
  • The feature matches the existing design system.
  • No existing functionality is broken.

Related features

How it works

  1. 1

    Copy the link

    Grab the Markdown instruction URL for this feature.

  2. 2

    Give it to your AI

    Paste it into Claude Code, Cursor, v0, Lovable — whatever you build with.

  3. 3

    It inspects, then implements

    Your agent reads your existing app first, then adds the feature to fit it.

Works with your stack

These instructions are written to adapt. They tell the agent to detect your framework, match your existing design system, and reuse what you already have — rather than assuming a particular stack.

Need it tighter than that? Customize the feature and tell it exactly what you're running.