Composition
Swap elements, compose with your router, merge styles.
Appica UI components ship fully styled, but you often need them to be something
else — a navigation link that's actually your router's Link, a tooltip trigger
that's actually your own button. The render prop, inherited from
Base UI, makes that possible without losing the
component's styling or behavior.
For the related case of a plain link that should merely look like a button, use the exported variant helpers instead — see Links that look like a button.
Rendering as a different element
Pass an element to render and the component adopts it as its output, merging in
its own props, classes, and event handlers. For instance, render a Badge as an
<a> so a status chip becomes a real link:
Don't render links as buttons
This is safe because Badge adds no interactive semantics. Button is different — it enforces button
semantics (role, keyboard handling), so rendering an <a> through it produces a link masquerading as a
button, which misleads assistive tech. To make a link merely look like a button, style the <a> directly —
see Links that look like a button.
Rendering as a component
render also accepts a component element — ideal for framework routers. A
NavigationLink stays a link while delegating navigation to your router's Link:
The same pattern composes the interactive parts of larger components. For instance, make a tooltip's trigger your own button:
Links that look like a button
Sometimes you don't want to change a component's element — you want a plain link
(or other element) to simply wear a component's styling. Don't reach for render
here: rendering an <a> through Button forces button semantics onto a link.
Instead, apply the exported buttonVariants classes directly to the element that
already has the right semantics:
Why not just render the link as a Button?
Links and buttons have different, non-interchangeable semantics: a link navigates, a button performs an
action. The render prop is for adopting a different element while keeping the component's role — not for
repainting a link as a button. Styling the <a> with buttonVariants keeps link semantics intact and gives
you the identical look.
buttonVariants isn't the only one — several components export their variant
recipe for exactly this purpose:
Each takes the same variant props as its component and returns a className string,
so they drop into any element and compose with your own classes through cn or a
template literal.
Where to put className
This is the one rule worth memorizing. When composing a render-prop wrapper:
- Visual overrides (
className,style) go on the wrapper. - Structural/behavioral props (
href,type,variant,size, rendering as a different element) go on the JSX insiderender.
Why this matters
Putting className on the inner JSX can cause a React hydration mismatch in Server Components: the styled element
is built on the server, serialized across the boundary, then cloned on the client, and the final class strings can
disagree. Keeping className on the wrapper avoids it — and class precedence still works, since the wrapper's classes
are merged last and win on conflicts.
If you specifically need a class on the inner element, extract the trigger into
its own 'use client' component rather than promoting the whole page to client.
Class merging
Overrides are merged with tailwind-merge, so conflicting utilities are de-duplicated and the last one wins. You don't need to fight specificity: