# DropdownMenu
URL: https://remocn.dev/docs/ui/components/dropdown-menu

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

- Install: `npx shadcn@latest add @remocn/dropdown-menu`
- Vibe: clean
- Natural length: 120f @ 30fps

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

```bash
npx shadcn@latest add @remocn/dropdown-menu
```

> **Note**
>
> 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:

```ts
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:

```tsx
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`:

```tsx
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](/docs/ui/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:

```tsx
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:

```ts
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

| Prop | Type | Default | Description |
|---|---|---|---|
| `state` | `"opened" \| "closed"` | `"closed"` | Current visual state (snap path). State changes snap — no automatic cross-fade. |
| `style` | `DropdownMenuStyle` | — | Resolved animated visual (smooth path). Pass an interpolated DropdownMenuStyle from useDropdownMenuTransition. Takes precedence over state when provided. |
| `label` | `string` | `"Options"` | Trigger button text. |
| `triggerStyle` | `ButtonStyle` | — | Resolved 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` | `-1` | Index of the row under the pointer or currently highlighted (hover state). -1 for none. |
| `pressedIndex` | `number` | `-1` | Index 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` | `string` | — | Optional className applied to the wrapper element. |

## Use when
- Showing a button that opens an action list — the classic "Options" or account menu pattern.
- Demonstrating hover/press states on individual menu items after the panel opens.
- Composing inside a nav bar or toolbar where a trigger button owns a small action list.

## Don't use when
- The menu is invoked by right-click at a cursor position — use `context-menu` instead.
- The list is searchable or filterable — use `combobox` instead.
- The overlay is a modal that blocks the scene — use `dialog` or `alert-dialog` instead.