Displays a menu to the user, such as a set of actions or functions, triggered by a button.
Installation
Install the packages
Add the shadcn-vue component
Animate it
The CLI finds your shadcn-vue components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.
Usage
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuTrigger,
} from '@animate-ui/vue/primitives/reka-ui';
</script>
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent :side-offset="4">
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>Check animation
Checkbox and radio items show their check in an DropdownMenuItemIndicator: 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:
<script setup lang="ts">
import { ref } from 'vue';
import {
DropdownMenuCheckboxItem,
DropdownMenuItemIndicator,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
} from '@animate-ui/vue/primitives/reka-ui';
const statusBar = ref(true);
const panel = ref('bottom');
</script>
<template>
<DropdownMenuCheckboxItem v-model="statusBar">
<DropdownMenuItemIndicator animation="pop"
><CheckIcon
/></DropdownMenuItemIndicator>
Status bar
</DropdownMenuCheckboxItem>
<DropdownMenuRadioGroup v-model="panel">
<DropdownMenuRadioItem value="top">
<DropdownMenuItemIndicator animation="pop"
><CheckIcon
/></DropdownMenuItemIndicator>
Top
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bottom">
<DropdownMenuItemIndicator animation="pop"
><CheckIcon
/></DropdownMenuItemIndicator>
Bottom
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</template>The same demo with check-animation="pop" (its Controls switch it back to draw):
To pop every check of your app at once (checkboxes, menus, selects), set check-animation="pop" on an AnimateUIProvider instead.
Animation
The motion of Animate UI's React Dropdown Menu:
- Content fades and scales in from 0.9, from the trigger (
--reka-dropdown-menu-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. - Stagger: opening from closed, the rows (items, labels, separators) come in one after another, 33ms apart (closer past 10 rows: 33ms × √(10 / rows), so a long list doesn't wait): each slides 8px in from the right and clears from a 4px blur as it fades in, in 0.3s on a soft ease-out (
{ duration: 0.3, ease: [0.215, 0.61, 0.355, 1] }). The box itself is there at once (its opacity in 0.1s, its scale as before), so the rows bring the fade, and a disabled row comes in at its own opacity. An item highlighted as it opens (the chosen one) shows its highlight once its row has landed. Reopened while it closes it doesn't stagger again, closing shows every row at once, and under reduced motion nothing staggers. - SubContent grows from the side of its trigger the same way.
- 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: 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. - 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 (orcheck-animation="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 (reka-ui'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 trigger at once instead of after the exit, and the leaving menu ignores the pointer and can't dismiss anything.
- With
force-mount(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 reka-ui would take the pointer down that opens it for one outside it, closing it at once. - Open from the start (
default-open), the menu plays its entrance on page load. - Under reduced motion, nothing scales or glides: the menu and the checks only fade, and the menu doesn't hold; the highlight jumps from item to item and only fades in when it appears.
API Reference
Every part takes exactly the props of reka-ui's Dropdown Menu. Animate UI adds the animation props below.
DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuPortal, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuSeparator, DropdownMenuArrow, DropdownMenuSub
reka-ui props only. They are not animated.
DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuSubTrigger
reka-ui props only. They take part in the highlight; it needs nothing more.
DropdownMenuContent, DropdownMenuSubContent
reka-ui props, plus:
| Prop | Type | Default |
|---|---|---|
transition? | Transition | { duration: 0.22, ease: [0.215, 0.61, 0.355, 1] } |
exit-transition? | Transition | transition |
DropdownMenuItemIndicator
reka-ui props, plus:
| Prop | Type | Default |
|---|---|---|
animation? | 'draw' | 'pop' | the closest AnimateUIProvider's check-animation, 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 |