Select

A dropdown select container with opened/closed states and item panel

A state atom representing a complete select dropdown: a trigger button and a panel of selectable 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/select

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

States

SelectState is:

type SelectState =
  | "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 { Select } from "@/components/remocn/select";

export const Scene = () => (
  <Select
    state="opened"
    label="Choose a fruit"
    items={["Apple", "Banana", "Orange"]}
    selectedIndex={1}
  />
);

To drive state from the timeline, use useCurrentState:

import { useCurrentState } from "@/lib/remocn-ui";
import { Select } from "@/components/remocn/select";

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

  return <Select 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 useSelectTransition from the copied use-select-transition.ts file. It reads the frame, interpolates between state presets, and returns a resolved SelectStyle — pass it to the style prop:

import { Select } from "@/components/remocn/select";
import { useSelectTransition } from "@/components/remocn/use-select-transition";

export const Scene = () => {
  const style = useSelectTransition([
    { at: 32, state: "opened", duration: 16 },
    { at: 92, state: "closed", duration: 12 },
  ]);
  return <Select 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-select-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 SelectItem rows — one per option. Each row has four states reflecting the user's interaction with that item:

SelectItemState is:

type SelectItemState =
  | "idle"     // neutral background, label foreground, check hidden
  | "hover"    // accent background, label foreground, check hidden
  | "press"    // darker accent background, label foreground, check hidden, scale 0.98
  | "selected" // accent background, accent-foreground label, check icon visible (opacity 1)

idle shows the default popover background. hover brightens to accent. press darkens further and shrinks to 0.98 scale. selected locks the accent background, switches the label to accent-foreground, and reveals the check icon at full opacity.

Preview a single SelectItem:

When you install select, the select-item atom is automatically pulled in via registryDependencies — no separate install needed. The Select container derives each row's state from the selectedIndex, 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
SelectStyleResolved animated visual (smooth path). Pass an interpolated SelectStyle from useSelectTransition. Takes precedence over state when provided.
label
string"Select a fruit"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[]["Apple", "Banana", "Orange", "Grape"]Array of option labels rendered in the panel.
selectedIndex
number-1Index of the persisted selection (shows check icon and accent color). -1 for no selection.
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
(SelectItemStyle | 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.