Menubar
Several dropdown menus that behave as one control.
src/components/rahti_ui/menubar.rs
The bar
Three menus, one control.
shadcn's own demo. Every menu here is the Dropdown Menu: a `<button popovertarget="…">`, a `popover`, and nothing else. Open one and then run the pointer along the bar — the menus hand off without a click, which is the one thing this component adds and the one thing the platform has no attribute for.
`File` stays highlighted while its menu is down, and no script does that either. `data-[state=open]:bg-accent` cannot cross — a trigger is never `:popover-open`, its menu is — but the menu is the trigger's next sibling, so `[&:has(+[popover]:popover-open)]:bg-accent` reaches it. That sibling relationship is the whole reason `MenubarMenu` renders a box at all.
Tab into the bar and out again: it is one stop, not three. The triggers become a roving tabindex when the script mounts — before that, all three are tabbable, which is a reasonable thing for a row of independent menus to be.
Ticks and dots
Checkbox rows, radio rows, and the inset that lines everything up.
A tick and a dot, both server-rendered from `checked` — the row's state is `aria-checked`, and the indicator is the whole of what shadcn draws.
The `People` heading and the rows beneath `Reload` are `inset`, which is `data-[inset]:pl-8` — a row with no tick lines up with the text of the rows that have one, rather than with their ticks.
These choice rows are `rounded-xs` where a Dropdown Menu's are `rounded-sm`. That is shadcn's difference, not the port's, and it is kept.
Submenus
Two levels down, and the one keyboard ambiguity a menubar has.
A submenu is the same two attributes one level down: its own `menu` name, its own `popovertarget`, its own `popover`. Nested popovers stay open when their invoker is inside the one above, which is the light-dismiss behaviour the platform already defines — two levels deep is the same rule twice.
Hover it and the submenu opens with no click, which the platform has no attribute for. Moving to another row closes it after a 150ms grace period, because the diagonal from this row to the menu beside it clips the row underneath on the way.
The keyboard has one ambiguity here and the pattern resolves it: ArrowRight on a submenu row goes *into* the submenu, and ArrowRight anywhere else goes to the menu next door. ArrowLeft is its mirror — out of a submenu, or one menu to the left.
A submenu sits flush against its parent rather than 4px away, because shadcn passes no `sideOffset` to a `MenubarSubContent` and Radix's default is zero. A Dropdown Menu's submenu is offset; this one is not, and that is shadcn's difference too.
Icons
Nothing to configure: the row's class list sizes and tones them.
shadcn's icons demo, with this library's icon set standing in for its lucide names. The sizing and the muted tone are the item's own class list — `[&_svg:not([class*='size-'])]:size-4` and its `text-` twin — so an icon needs nothing said about it, and a destructive row recolours its icon too.
`Find in Files` has an `href`, so it renders as `<a role="menuitem">` rather than shadcn's `<div>` and the browser activates it. That row works with the runtime absent, which — since the open and close already do — means this whole menu does.
What the platform does
Six of the ten behaviours, before a line of JavaScript.
The first six rows are why an unmounted menubar is not a broken control: it is a row of independent dropdown menus, each of which opens, closes, dismisses and highlights itself correctly. The last four are what makes them one bar, and none of them is expressible in markup.
The port
What crossed, what the popover made unnecessary, and where shadcn's menubar differs from its dropdown.
The first menu on this page carries `id="menubar-file"` and its trigger `popovertarget="menubar-file"`. That name is the whole API surface between the two halves, which is why it has to be unique — and why a component that renders its children twice, as the Sidebar does for its mobile drawer, would otherwise put two menus in one document under one name. The script renames the second at mount.
The two offsets, spelled out: a menu hangs 8px below the bar and slides -4px towards its trigger's start edge, which lines the menu's padding up with the trigger's text rather than with the trigger's box. The trigger has `px-2` of its own, so without the second number the labels would not line up.