A menu bar providing commands and options for your application.
Installation
Install the packages
Add the shadcn/ui component
Animate it
The CLI finds your shadcn/ui components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.
Usage
Import Menubar and Menu from Animate UI instead of Base UI. Everything else stays the same.
import { Menu } from '@animate-ui/react/primitives/base-ui/menu';
import { Menubar } from '@animate-ui/react/primitives/base-ui/menubar';
export function EditorMenubar() {
return (
<Menubar>
<Menu.Root>
<Menu.Trigger>File</Menu.Trigger>
<Menu.Portal>
<Menu.Positioner sideOffset={8}>
<Menu.Popup>
<Menu.Item>New file</Menu.Item>
<Menu.Item>Save</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
<Menu.Root>
<Menu.Trigger>Edit</Menu.Trigger>
<Menu.Portal>
<Menu.Positioner sideOffset={8}>
<Menu.Popup>
<Menu.Item>Undo</Menu.Item>
<Menu.Item>Redo</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
</Menubar>
);
}Using the @base-ui/react umbrella import instead? The barrel mirrors it: import { Menu, Menubar } from '@animate-ui/react/primitives/base-ui'.
Menubar takes Base UI's props as they are and lets its menus hand their popover over to one another: each menu is an animated Menu. Take both from Animate UI: under Base UI's own Menubar, each menu closes and opens on its own.
Check animation
Checkbox and radio items show their check in a Menu's CheckboxItemIndicator or RadioItemIndicator: it draws itself when the item is checked, and erases itself when it's unchecked (a radio item's while the new one draws). With animation="pop", it pops in from half its size and shrinks back out instead, and a radio item's check glides to the newly checked item:
import { Menu } from '@animate-ui/react/primitives/base-ui/menu';
import { Menubar } from '@animate-ui/react/primitives/base-ui/menubar';
export function ViewMenubar() {
return (
<Menubar>
<Menu.Root>
<Menu.Trigger>View</Menu.Trigger>
<Menu.Portal>
<Menu.Positioner sideOffset={8}>
<Menu.Popup>
<Menu.CheckboxItem defaultChecked>
<Menu.CheckboxItemIndicator animation="pop">
<CheckIcon />
</Menu.CheckboxItemIndicator>
Status bar
</Menu.CheckboxItem>
<Menu.Separator />
<Menu.RadioGroup defaultValue="bottom">
<Menu.RadioItem value="top">
<Menu.RadioItemIndicator animation="pop">
<CheckIcon />
</Menu.RadioItemIndicator>
Top
</Menu.RadioItem>
<Menu.RadioItem value="bottom">
<Menu.RadioItemIndicator animation="pop">
<CheckIcon />
</Menu.RadioItemIndicator>
Bottom
</Menu.RadioItem>
</Menu.RadioGroup>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
</Menubar>
);
}The same demo with checkAnimation="pop" (its Controls switch it back to draw):
To pop every check of your app at once (checkboxes, menus, selects, comboboxes), set checkAnimation="pop" on an AnimateUIProvider instead.
Animation
Base UI's Menubar has no Animate UI v1 counterpart: each menu opens and closes like v1's Base UI Menu, and moving between menus hands one popover from menu to menu.
- Opening the bar fades and scales the menu in from 0.9 in 220ms (a soft ease-out), from its trigger; closing it takes 220ms too.
- Moving from one open menu to another (hovering, clicking or tapping a trigger, or pressing the arrow keys), the popover doesn't close and reopen: it moves to the next menu and resizes on a spring (stiffness 660, damping 52): it starts gently, is most of the way there in ~155ms and eases in without going past, settled by ~300ms (the Popup's
transition, if you set one), sliding over the content, which stays where it is on screen. Going from File to Edit, File's items leave to the left, 6px, while they fade out (220ms, a soft ease-out), and Edit's come in from 6px to the right on a spring (stiffness 450, damping 43) and fade in behind them (260ms, on a slow start); going back, the other way around. The popover clips them as it goes. - The popover that moves is the next menu's Popup, interactive from its first frame: it starts where the previous one was, at its size and opacity, without an entrance, and the previous menu (with its open submenus) is hidden in that frame and unmounted at once, so there's one popover on screen however fast the pointer sweeps the bar, whichever order Base UI closes and opens them in. A copy of the previous items (
aria-hidden,inert) leaves inside it. It moves and resizes on Web Animations (translate, added to its own, andwidth/height, its margins making up the difference so Base UI places it once, above the bar or against an edge as well), never scaled, so the items keep their size, and nothing is left on it once it has landed. Moving on again before it has landed, it leaves from where it is, with what it shows, keeping its speed toward the next menu (as much of it as never takes it past that menu; sent back the way it came, it turns back at once), so sweeping across the bar is one glide. Going back and forth faster than the content settles, each menu's content shows once: what's still leaving goes on leaving from where it is, and back to a menu whose items are still leaving, they come back from there (where they are, as they show) instead of coming in again. With atransitionof 0, it moves and swaps at once. - The triggers' highlight: the triggers share one highlight, like a menu's items: a trigger hovered, focused or with its menu open wears its own background (
hover:bg-muted,aria-expanded:bg-muted…, read from your styles and cleared on the trigger), and that background glides from trigger to trigger on the highlight's spring (stiffness 500, damping 40), along with the popover when a menu is open. It fades in (200ms, on CSS'sease) when it appears from nothing and fades out where it is the same way when no trigger is lit anymore (the pointer leaving the bar with no menu open, a menu closed); lit again meanwhile, it comes back from there. The bar becomes its own stacking context (isolation: isolate), so the highlight goes under the triggers' text and above the bar's background. - A menu open from the start (
defaultOpenon itsMenu.Root) plays its entrance on page load, like v1's menus. - Under reduced motion, nothing moves between menus: each one fades in place, the next in as the previous one fades out; the triggers' highlight jumps from trigger to trigger, and only fades.
- Everything else works like the Menu: the highlight gliding behind the highlighted item, submenus, item indicators, focus and reduced motion.
API Reference
Every part takes exactly the props of Base UI's Menubar and Menu.
Menubar
Base UI props only. It is not animated itself: its menus hand their popover over through it, and its menus' triggers (each menu's Menu.Trigger, nothing more needed) share one highlight through it.
Menu parts
See the Menu page: Popup takes transition and exitTransition, CheckboxItemIndicator and RadioItemIndicator take animation and transition. In a Menubar, the Popup's transition, if you set one, also moves and resizes the popover to the next menu (by default, a spring: { type: 'spring', stiffness: 660, damping: 52 }).