rules/base-ui-components.md
This project uses Base UI (@base-ui/react) for all headless UI primitives. Do not use Radix UI (@radix-ui/*) for any new components. This ensures:
If you need a component not yet wrapped in src/components/ui/, build it using Base UI primitives following the existing patterns in that directory.
The ContextMenu in src/components/ui/context-menu.tsx uses Base UI's native ContextMenu primitive (@base-ui/react/context-menu), which handles right-click and long-press detection automatically. Key differences from Radix's API:
onClick instead of onSelect on ContextMenuItemContextMenuTrigger renders a <div> wrapper — no asChild needed (use the render prop if you need to change the element type)// Correct usage
<ContextMenu>
<ContextMenuTrigger>
<div>Right-click me</div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem onClick={() => doSomething()}>Action</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
TooltipTrigger from @base-ui/react/tooltip (wrapped in src/components/ui/tooltip.tsx) renders a <button> by default. Wrapping another button-like element (<button>, <Button>, <DropdownMenuTrigger>, <PopoverTrigger>, <MiniSelectTrigger>, <ToggleGroupItem>) inside it creates invalid nested <button> HTML. Use the render prop instead:
// Wrong: nested buttons
<TooltipTrigger><Button onClick={fn}>Click</Button></TooltipTrigger>
// Correct: render prop merges into a single element
<TooltipTrigger render={<Button onClick={fn} />}>Click</TooltipTrigger>
ToggleGroupItem in TooltipTrigger without render also breaks :first-child/:last-child CSS selectors for rounded corners on the group.title attribute over Tooltip — tooltips appear immediately on hover and interfere with drag interactions, while title has a built-in delay.The Accordion component in src/components/ui/accordion.tsx wraps @base-ui/react/accordion, not Radix or shadcn. The APIs differ:
type or collapsible props — these are Radix/shadcn-only. Reviewers may suggest type="single" collapsible but these props don't exist on Base UI's Accordion.multiple (boolean, default false) to allow multiple items open at once.defaultValue (array of item values) to control which items start expanded.