Composition
One polymorphism mechanism, matching Base UI. A dialog title is not a new class —
render, never asChild
One polymorphism mechanism, matching Base UI. A dialog title is not a new class — it is the text primitive wearing the dialog's a11y wiring:
const DialogTitle = (props) => (
<DialogPrimitive.Title render={<Text.Heading as="h2" />} {...props} />
)Behavior and accessibility come from the library; appearance comes from the
design system; render stitches them. Fusing both into a CSS class is what
produces 17 title classes in the baseline.
When parts are identical, share the wrapper
Some primitives are literally the same object across components — in Base UI,
ContextMenu.Item === Menu.Item, with only Root and Trigger differing.
There, write the wrappers once as a factory and inject the namespace:
export const ContextMenu = {
Root: CM.Root,
Trigger: CM.Trigger,
...createMenuParts(CM),
}Injecting rather than importing one namespace directly means that if the library ever diverges, each component inherits the divergence instead of being silently pinned to the wrong primitive.
A control never carries its own body
Border, background, height and focus ring belong to the field frame, never to
the input. This is what collapses three ways of writing an input into one.
shadcn needs a second border-less input because its Input carries a border,
which would double inside a group. With the body owned by the frame, a new
control — numeric, multi-select, color picker — is only its own middle.
One export per component
DropdownMenu.Item, not DropdownMenuItem. You remember the component, not
its fifteen part names. This costs tree-shaking, which is acceptable: parts
of one menu are used together, and copy-paste distribution only ships what is
installed.