A visually persistent menu common in desktop applications that provides quick access to a consistent set of commands.
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 the primitive from Animate UI instead of Radix UI. Everything else stays the same.
import { Menubar } from '@animate-ui/react/primitives/radix-ui';
export function EditorMenubar() {
return (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger>File</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content align="start" sideOffset={8}>
<Menubar.Item>New file</Menubar.Item>
<Menubar.Item>Save</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger>Edit</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content align="start" sideOffset={8}>
<Menubar.Item>Undo</Menubar.Item>
<Menubar.Item>Redo</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
}Coming from the @radix-ui/react-menubar package? Use the subpath: import * as MenubarPrimitive from '@animate-ui/react/primitives/radix-ui/menubar'.
Check animation
Checkbox and radio items show their check in an ItemIndicator: 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 * as React from 'react';
import { Menubar } from '@animate-ui/react/primitives/radix-ui';
export function ViewMenubar() {
const [statusBar, setStatusBar] = React.useState(true);
const [panel, setPanel] = React.useState('bottom');
return (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger>View</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content align="start" sideOffset={8}>
<Menubar.CheckboxItem
checked={statusBar}
onCheckedChange={setStatusBar}
>
<Menubar.ItemIndicator animation="pop">
<CheckIcon />
</Menubar.ItemIndicator>
Status bar
</Menubar.CheckboxItem>
<Menubar.Separator />
<Menubar.RadioGroup value={panel} onValueChange={setPanel}>
<Menubar.RadioItem value="top">
<Menubar.ItemIndicator animation="pop">
<CheckIcon />
</Menubar.ItemIndicator>
Top
</Menubar.RadioItem>
<Menubar.RadioItem value="bottom">
<Menubar.ItemIndicator animation="pop">
<CheckIcon />
</Menubar.ItemIndicator>
Bottom
</Menubar.RadioItem>
</Menubar.RadioGroup>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
}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
Radix's Menubar has no Animate UI v1 counterpart: each menu opens and closes like v1's Dropdown Menu, and moving between menus hands one popover from menu to menu.
- Content fades and scales in from 0.9, from its trigger (
--radix-menubar-content-transform-origin, set inline so it works without styles), in 220ms ({ duration: 0.22, ease: [0.215, 0.61, 0.355, 1] }, a soft ease-out), and closes the same way before it unmounts. - SubContent grows from the side of its trigger the same way.
- 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 Content'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 Content, 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. 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 Radix 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 highlight: one surface glides behind the highlighted item on a spring (stiffness 500, damping 40), following the pointer and the arrow keys alike, and into submenus. Moving to another item, it carries on from where it is, at its speed: however fast the pointer or the arrow keys go, there's one highlight on screen. It only fades in (200ms, on CSS's
ease) when it appears from nothing, and its color changes the same way when the item's highlighted background differs from the previous one's. It glides on the compositor (a Web Animation oftransform), so a busy main thread, a submenu opening for instance, doesn't stall it. Into a submenu or back out of it, each menu clips its own part, as in v1: the previous surface glides along with the new one, the same box on screen, so the two make one highlight crossing from one menu into the other; the entering part fades in as it glides. When the pointer leaves the menu, it stays on the last item. It wears the item's own highlighted background (focus:bg-accent,data-highlighted:bg-accent…), read from your styles: the item's background is cleared while it's highlighted (or still has the focus, or its submenu is open), and afocus:background is read again once the item has the focus; its text colors stay. - The triggers' highlight: the triggers share one highlight too, like the 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. - ItemIndicator: an item's check draws itself like a Checkbox's check (its stroke and opacity in 0.3s on Motion's easeOut, at once: there's no box to fill first) when the item is checked, and erases itself in 0.3s when it's unchecked (it stays mounted for that). Checking another radio item erases the old check while the new one draws itself. Only checking and unchecking animate: opening the menu shows its checks at rest, and closing it leaves them as they are.
- With
animation="pop"on the ItemIndicator (orcheckAnimation="pop"on anAnimateUIProvider), checks pop instead: in from half their size out of a 2px blur (a spring, stiffness 500, damping 24), and back into their blur in 0.15s. A radio item's check doesn't pop from one item to another: it glides from the item it leaves to the newly checked one, on the highlight's spring (stiffness 500, damping 40), like a select's. The demo's Controls switch between the two. - Choosing an item closes the menu (Radix's default): when its check changes (a checkbox item's, or another radio item's than the checked one), the menu holds still first (250ms while a check draws itself, 150ms while it pops or glides), so the change is seen, then fades out. Escape and outside clicks don't wait.
- When the menu closes with focus inside, focus goes back to the menu's trigger at once instead of after the exit, and the leaving menu ignores the pointer and can't dismiss anything.
- With
forceMount(on the Content or the Portal), the closed content stays in the DOM: it plays its exit, then is hidden (visibility: hidden). It still opens on a click: closed, it's a dismissable layer already, and Radix would take the pointer down that opens it for one outside it, closing it at once. - A menu open from the start (the Root's
defaultValue) plays its entrance on page load, like v1's menus. - Under reduced motion, nothing scales, glides or slides: the menu and the checks only fade, and the menu doesn't hold; moving to another menu, each one fades in place, the next in as the previous one fades out; the highlights jump from item to item and from trigger to trigger, and only fade.
API Reference
Every part takes exactly the props of Radix UI's Menubar. Animate UI adds the animation props below.
Root, Menu, Portal, Group, Label, RadioGroup, Separator, Arrow, Sub
Radix UI props only. They are not animated.
Trigger
Radix UI props only. It takes part in the triggers' highlight; it needs nothing more.
Item, CheckboxItem, RadioItem, SubTrigger
Radix UI props only. They take part in the highlight; it needs nothing more.
Content, SubContent
Radix UI props, plus:
| Prop | Type | Default |
|---|---|---|
transition? | Transition | { duration: 0.22, ease: [0.215, 0.61, 0.355, 1] } (the move: { type: 'spring', stiffness: 660, damping: 52 }) |
exitTransition? | Transition | transition |
ItemIndicator
Radix UI props, plus:
| Prop | Type | Default |
|---|---|---|
animation? | 'draw' | 'pop' | the closest AnimateUIProvider's checkAnimation, else 'draw' |
transition? | Transition | draw: 0.3s on Motion's easeOut both ways (a Checkbox's, without its delay); pop: a spring (stiffness 500, damping 24) in, 0.15s out, and the highlight's spring (stiffness 500, damping 40) for the glide |