DropdownMenu

A dropdown menu container with opened/closed states and action rows

A state atom representing a complete dropdown menu: a trigger button and a panel of actionable items. Its appearance (opened, closed) is a pure function of the state prop. Pass a state directly or drive it from the Remotion timeline via useCurrentState. The component reads no frame — only the state value you give it. The panel reveals below the trigger with opacity, scale, and vertical translation animations.

Installation

$ pnpm dlx shadcn@latest add @remocn/dropdown-menu

Installing dropdown-menu automatically installs the shared remocn-ui core (lib/remocn-ui/), the button primitive, and the dropdown-menu-item atom via registryDependencies (["remocn-ui", "button", "dropdown-menu-item"]). You do not need to install them separately. The trigger reuses the Button outline variant, and the panel renders a column of DropdownMenuItem rows.

States

DropdownMenuState is:

type DropdownMenuState =
  | "opened"  // panel visible, opacity 1, scale 1, translateY 0, chevron rotated 180°
  | "closed"  // panel hidden, opacity 0, scale 0.96, translateY -4px, chevron at 0°

opened reveals the panel with the trigger chevron pointing up (rotated 180°). closed hides the panel and returns the chevron to its downward point.

Snap usage

Pass state directly — the component snaps instantly to that visual. Useful for static previews or when you drive state from your own logic:

import { DropdownMenu } from "@/components/remocn/dropdown-menu";

export const Scene = () => (
  <DropdownMenu
    state="opened"
    label="More"
    items={["Profile", "Settings", "Log out"]}
  />
);

To drive state from the timeline, use useCurrentState:

import { useCurrentState } from "@/lib/remocn-ui";
import { DropdownMenu } from "@/components/remocn/dropdown-menu";

export const Scene = () => {
  const state = useCurrentState(
    [
      { at: 32, state: "opened" },
      { at: 92, state: "closed" },
    ],
    "closed",
  );

  return <DropdownMenu state={state} />;
};

Each at is a Sequence-local authored frame. State persists between steps: opened at frame 32 keeps the panel visible until closed fires at frame 92. See Concepts for the full useCurrentState API.

Smooth transitions

State changes via state snap with no cross-fade. For animated transitions, use useDropdownMenuTransition from the copied use-dropdown-menu-transition.ts file. It reads the frame, interpolates between state presets, and returns a resolved DropdownMenuStyle — pass it to the style prop:

import { DropdownMenu } from "@/components/remocn/dropdown-menu";
import { useDropdownMenuTransition } from "@/components/remocn/use-dropdown-menu-transition";

export const Scene = () => {
  const style = useDropdownMenuTransition([
    { at: 32, state: "opened", duration: 16 },
    { at: 92, state: "closed", duration: 12 },
  ]);
  return <DropdownMenu style={style} />;
};

The duration field on each step overrides the file's DEFAULT_DURATION (= 12) for that specific transition. To globally tune timing and easing, edit use-dropdown-menu-transition.ts directly in your project — that file is yours (shadcn "own your code" philosophy).

style takes precedence over state when both are provided.

Items

The panel renders a column of DropdownMenuItem rows — one per action. Each row has three states reflecting the user's interaction with that item:

DropdownMenuItemState is:

type DropdownMenuItemState =
  | "idle"   // neutral popover background, popover-foreground label
  | "hover"  // accent background, popover-foreground label
  | "press"  // darker accent background, popover-foreground label, scale 0.98

idle shows the default popover background. hover brightens to accent. press darkens and shrinks to 0.98 scale. Unlike SelectItem, DropdownMenuItem has no selected state — menu items are stateless after interaction.

Preview a single DropdownMenuItem:

When you install dropdown-menu, the dropdown-menu-item atom is automatically pulled in via registryDependencies — no separate install needed. The DropdownMenu container derives each row's state from the highlightedIndex and pressedIndex props, or you can override a row's visual with itemStyles.

Props

PropTypeDefaultDescription
state
"opened" | "closed""closed"Current visual state (snap path). State changes snap — no automatic cross-fade.
style
DropdownMenuStyleResolved animated visual (smooth path). Pass an interpolated DropdownMenuStyle from useDropdownMenuTransition. Takes precedence over state when provided.
label
string"Options"Trigger button text.
triggerStyle
ButtonStyleResolved Button visual for the trigger (smooth path). Drive it with useButtonTransition for a hover/press click animation. Defaults to the resting Button outline look.
items
string[]["Profile", "Billing", "Settings", "Log out"]Array of action labels rendered in the panel.
highlightedIndex
number-1Index of the row under the pointer or currently highlighted (hover state). -1 for none.
pressedIndex
number-1Index of the row being pressed (press state). -1 for none.
itemStyles
(DropdownMenuItemStyle | undefined)[]Per-item resolved visual override (smooth path). When an entry is present it wins over the index→state derivation. Used by the live example to animate a single row.
theme
Partial<RemocnTheme>Per-component theme override. Merges with the active RemocnUIProvider theme and the mode defaults. Must be concrete oklch/hex/rgb values — not CSS custom properties.
mode
"light" | "dark""light"Sets the base palette. Overrides the mode on RemocnUIProvider when both are present.
className
stringOptional className applied to the wrapper element.