shadcn-remix
GitHub

Dropdown Menu

Displays a menu to the user — such as a set of actions or functions — triggered by a button.

import { css } from 'remix/component'

import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
  DropdownMenuContent,
import { css } from 'remix/component'

import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuPortal,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from '#app/ui/dropdown-menu.tsx'

export function DropdownMenuDemo() {
  return () => (
    <DropdownMenu>
      <DropdownMenuTrigger mix={buttonVariants({ variant: 'outline' })}>Open</DropdownMenuTrigger>
      <DropdownMenuContent mix={w56} align="start">
        <DropdownMenuLabel>My Account</DropdownMenuLabel>
        <DropdownMenuGroup>
          <DropdownMenuItem>
            Profile
            <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            Billing
            <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            Settings
            <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            Keyboard shortcuts
            <DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
          </DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuItem>Team</DropdownMenuItem>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
            <DropdownMenuPortal>
              <DropdownMenuSubContent>
                <DropdownMenuItem>Email</DropdownMenuItem>
                <DropdownMenuItem>Message</DropdownMenuItem>
                <DropdownMenuSeparator />
                <DropdownMenuItem>More...</DropdownMenuItem>
              </DropdownMenuSubContent>
            </DropdownMenuPortal>
          </DropdownMenuSub>
          <DropdownMenuItem>
            New Team
            <DropdownMenuShortcut>⌘+T</DropdownMenuShortcut>
          </DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuItem>GitHub</DropdownMenuItem>
        <DropdownMenuItem>Support</DropdownMenuItem>
        <DropdownMenuItem disabled>API</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem>
          Log out
          <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

const w56 = css({ width: '14rem' })

Installation

$ pnpm dlx shadcn@latest add https://www.shadcn-remix.com/r/dropdown-menu.json

Usage

import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '#app/ui/dropdown-menu.tsx'
<DropdownMenu>
  <DropdownMenuTrigger mix={buttonVariants({ variant: 'outline' })}>Open</DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuGroup>
      <DropdownMenuLabel>My Account</DropdownMenuLabel>
      <DropdownMenuItem>Profile</DropdownMenuItem>
      <DropdownMenuItem>Billing</DropdownMenuItem>
    </DropdownMenuGroup>
    <DropdownMenuSeparator />
    <DropdownMenuGroup>
      <DropdownMenuItem>Team</DropdownMenuItem>
      <DropdownMenuItem>Subscription</DropdownMenuItem>
    </DropdownMenuGroup>
  </DropdownMenuContent>
</DropdownMenu>

Examples

Checkboxes

Use DropdownMenuCheckboxItem for toggles.

import { css, type Handle } from 'remix/component'

import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
import { css, type Handle } from 'remix/component'

import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '#app/ui/dropdown-menu.tsx'

export function DropdownMenuCheckboxes(handle: Handle) {
  let showStatusBar = true
  let showActivityBar = false
  let showPanel = false

  return () => (
    <DropdownMenu>
      <DropdownMenuTrigger mix={buttonVariants({ variant: 'outline' })}>Open</DropdownMenuTrigger>
      <DropdownMenuContent mix={w56}>
        <DropdownMenuLabel>Appearance</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuCheckboxItem
          checked={showStatusBar}
          onCheckedChange={(checked) => {
            showStatusBar = checked
            handle.update()
          }}
        >
          Status Bar
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem
          checked={showActivityBar}
          onCheckedChange={(checked) => {
            showActivityBar = checked
            handle.update()
          }}
          disabled
        >
          Activity Bar
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem
          checked={showPanel}
          onCheckedChange={(checked) => {
            showPanel = checked
            handle.update()
          }}
        >
          Panel
        </DropdownMenuCheckboxItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

const w56 = css({ width: '14rem' })

Radio Group

Use DropdownMenuRadioGroup for exclusive choices.

import { css, type Handle } from 'remix/component'

import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
  DropdownMenuContent,
import { css, type Handle } from 'remix/component'

import { buttonVariants } from '#app/ui/button.tsx'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '#app/ui/dropdown-menu.tsx'

export function DropdownMenuRadioGroupExample(handle: Handle) {
  let position = 'bottom'

  return () => (
    <DropdownMenu>
      <DropdownMenuTrigger mix={buttonVariants({ variant: 'outline' })}>Open</DropdownMenuTrigger>
      <DropdownMenuContent mix={w56}>
        <DropdownMenuLabel>Panel Position</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuRadioGroup
          value={position}
          onValueChange={(value) => {
            position = value
            handle.update()
          }}
        >
          <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

const w56 = css({ width: '14rem' })

Dialog

import { css, type Handle } from 'remix/component'

import { Button, buttonVariants } from '#app/ui/button.tsx'
import {
  Dialog,
  DialogClose,
import { css, type Handle } from 'remix/component'

import { Button, buttonVariants } from '#app/ui/button.tsx'
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '#app/ui/dialog.tsx'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuTrigger,
} from '#app/ui/dropdown-menu.tsx'
import { Field, FieldGroup, FieldLabel } from '#app/ui/field.tsx'
import { MoreHorizontalIcon } from '#app/ui/icons.tsx'
import { Input } from '#app/ui/input.tsx'
import { Label } from '#app/ui/label.tsx'
import { Textarea } from '#app/ui/textarea.tsx'

const dialogWidth = css({ '@media (min-width: 40rem)': { maxWidth: '425px' } })

export function DropdownMenuDialog(handle: Handle) {
  let showNewDialog = false
  let showShareDialog = false

  return () => (
    <>
      <DropdownMenu>
        <DropdownMenuTrigger aria-label="Open menu" mix={buttonVariants({ variant: 'outline', size: 'icon-sm' })}>
          <MoreHorizontalIcon />
        </DropdownMenuTrigger>
        <DropdownMenuContent mix={css({ width: '10rem' })} align="end">
          <DropdownMenuLabel>File Actions</DropdownMenuLabel>
          <DropdownMenuGroup>
            <DropdownMenuItem
              onSelect={() => {
                showNewDialog = true
                handle.update()
              }}
            >
              New File...
            </DropdownMenuItem>
            <DropdownMenuItem
              onSelect={() => {
                showShareDialog = true
                handle.update()
              }}
            >
              Share...
            </DropdownMenuItem>
            <DropdownMenuItem disabled>Download</DropdownMenuItem>
          </DropdownMenuGroup>
        </DropdownMenuContent>
      </DropdownMenu>
      <Dialog
        open={showNewDialog}
        onOpenChange={(open) => {
          showNewDialog = open
          handle.update()
        }}
      >
        <DialogContent mix={dialogWidth}>
          <DialogHeader>
            <DialogTitle>Create New File</DialogTitle>
            <DialogDescription>
              Provide a name for your new file. Click create when you&apos;re done.
            </DialogDescription>
          </DialogHeader>
          <FieldGroup mix={css({ paddingBottom: '0.75rem' })}>
            <Field>
              <FieldLabel htmlFor="filename">File Name</FieldLabel>
              <Input id="filename" name="filename" placeholder="document.txt" />
            </Field>
          </FieldGroup>
          <DialogFooter>
            <DialogClose mix={buttonVariants({ variant: 'outline' })}>Cancel</DialogClose>
            <Button type="submit">Create</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
      <Dialog
        open={showShareDialog}
        onOpenChange={(open) => {
          showShareDialog = open
          handle.update()
        }}
      >
        <DialogContent mix={dialogWidth}>
          <DialogHeader>
            <DialogTitle>Share File</DialogTitle>
            <DialogDescription>Anyone with the link will be able to view this file.</DialogDescription>
          </DialogHeader>
          <FieldGroup mix={css({ paddingBlock: '0.75rem' })}>
            <Field>
              <Label htmlFor="email">Email Address</Label>
              <Input id="email" name="email" type="email" placeholder="shadcn@vercel.com" autoComplete="off" />
            </Field>
            <Field>
              <FieldLabel htmlFor="message">Message (Optional)</FieldLabel>
              <Textarea id="message" name="message" placeholder="Check out this file" />
            </Field>
          </FieldGroup>
          <DialogFooter>
            <DialogClose mix={buttonVariants({ variant: 'outline' })}>Cancel</DialogClose>
            <Button type="submit">Send Invite</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  )
}

API Reference

PropTypeDefault
openboolean-
defaultOpenboolean-
onOpenChange((open: boolean) => void)-

Radix portals menu content; here content renders in the top layer (popover) instead.

PropTypeDefault
mixMixValue-
PropTypeDefault
sideSide"bottom"
alignAlign"center"
sideOffsetnumber4
alignOffsetnumber0
mixMixValue-
PropTypeDefault
mixMixValue-
PropTypeDefault
insetboolean-
variant"default" | "destructive""default"
disabledbooleanfalse
textValuestring-
onSelect((event: MenuSelectEvent) => void)-
mixMixValue-
PropTypeDefault
checkedboolean | "indeterminate"false
onCheckedChange((checked: boolean) => void)-
disabledbooleanfalse
textValuestring-
onSelect((event: MenuSelectEvent) => void)-
mixMixValue-
PropTypeDefault
valuestring-
onValueChange((value: string) => void)-
mixMixValue-
PropTypeDefault
valuestring-
disabledbooleanfalse
textValuestring-
onSelect((event: MenuSelectEvent) => void)-
mixMixValue-
PropTypeDefault
insetboolean-
mixMixValue-
PropTypeDefault
mixMixValue-
PropTypeDefault
mixMixValue-
PropTypeDefault
openboolean-
defaultOpenboolean-
onOpenChange((open: boolean) => void)-
PropTypeDefault
insetboolean-
disabledbooleanfalse
textValuestring-
mixMixValue-
PropTypeDefault
sideOffsetnumber0
alignOffsetnumber0
mixMixValue-