Style families
Components sharing a visual skeleton share a **family** — one source of style, consumed by all of them.
Components sharing a visual skeleton share a family — one source of style, consumed by all of them.
popup the floating box dropdown, context, menubar, select, combobox, popover
menu the option list dropdown, context, menubar, select, combobox, command
field the input control input, textarea, select trigger
layer backdrop + panel dialog, alert-dialog, sheet, drawerFamilies are orthogonal, not a hierarchy: select is field + popup +
menu. A new component is a recombination.
Zero variants, variation as a named member
export const menu = {
item: tv({ base: "…" }),
selectableItem: tv({ extend: item, base: "pe-8 ps-1.5" }), // has an indicator
}As a CSS override, pe-8 is silent — you cannot tell intent from drift. As a
named member it documents itself, and drift becomes impossible by
construction.
A variant is only justified when two components need different values and both are right. If one side is arbitrary, unify.
Normalize state before sharing
Libraries disagree on what "the active item" is — :focus,
[data-highlighted], [data-selected]. While three selectors mean one
thing, shared style cannot be written once. Normalize in CSS, in one place:
@custom-variant highlighted (&:is(:focus, [data-highlighted], [data-selected]));A new library is one more selector here. This is cheaper than a React translation layer per component.
Palettes
The palette set: chromatic palettes, where the colour is the element, and surface-ring palettes, neutral surfaces with a chromatic focus ring.
Composition
How components compose: `render` instead of `asChild`, one wrapper for identical parts, the body on the field frame, one export per component.