Fragiola UI
Architecture

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.

On this page