Dropdown Menu

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

import { Button } from "~/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuPortal,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger
} from "~/components/ui/dropdown-menu"

export function DropdownMenuDemo() {
  return (
    <DropdownMenu placement="bottom-start">
      <DropdownMenuTrigger as={Button<"button">} variant="outline">
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent class="w-56">
        <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 overlap>
            <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>
        <DropdownMenuItem variant="destructive">
          Delete Account
          <DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

Installation

CLI

Manual

Install the following dependencies:

Copy and paste the following code into your project.

dropdown-menu.tsx
import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js"
import { mergeProps, splitProps } from "solid-js"
import * as DropdownMenuPrimitive from "@kobalte/core/dropdown-menu"
import type { PolymorphicProps } from "@kobalte/core/polymorphic"
import { cn } from "~/lib/utils"
const DropdownMenu: Component<DropdownMenuPrimitive.DropdownMenuRootProps> = (props) => {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" gutter={4} {...props} />
}
const DropdownMenuPortal: Component<DropdownMenuPrimitive.DropdownMenuPortalProps> = (props) => (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
)
const DropdownMenuTrigger = <T extends ValidComponent = "button">(
props: PolymorphicProps<T, DropdownMenuPrimitive.DropdownMenuTriggerProps<T>>
) => <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
type DropdownMenuContentProps<T extends ValidComponent = "div"> =
DropdownMenuPrimitive.DropdownMenuContentProps<T> & {
class?: string | undefined
}
const DropdownMenuContent = <T extends ValidComponent = "div">(
props: PolymorphicProps<T, DropdownMenuContentProps<T>>
) => {
const [local, others] = splitProps(props as DropdownMenuContentProps, ["class"])
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
class={cn(
"data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 z-50 max-h-(--kb-popper-content-available-height) min-w-[8rem] origin-(--kb-menu-content-transform-origin) overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[closed]:animate-out data-[expanded]:animate-in",
local.class
)}
data-slot="dropdown-menu-content"
{...others}
/>
</DropdownMenuPrimitive.Portal>
)
}
const DropdownMenuGroup: Component<DropdownMenuPrimitive.DropdownMenuGroupProps> = (props) => (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
type DropdownMenuItemProps<T extends ValidComponent = "div"> =
DropdownMenuPrimitive.DropdownMenuItemProps<T> & {
class?: string | undefined
variant?: "default" | "destructive"
}
const DropdownMenuItem = <T extends ValidComponent = "div">(
rawProps: PolymorphicProps<T, DropdownMenuItemProps<T>>
) => {
const props = mergeProps({ variant: "default" }, rawProps)
const [local, others] = splitProps(props as DropdownMenuItemProps, ["class", "variant"])
return (
<DropdownMenuPrimitive.Item
class={cn(
"data-[variant=destructive]:*:[svg]:!text-destructive relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[disabled]:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
local.class
)}
data-slot="dropdown-menu-item"
data-variant={local.variant}
{...others}
/>
)
}
const DropdownMenuShortcut: Component<ComponentProps<"span">> = (props) => {
const [local, others] = splitProps(props, ["class"])
return (
<span
class={cn("ml-auto text-muted-foreground text-xs tracking-widest", local.class)}
data-slot="dropdown-menu-shortcut"
{...others}
/>
)
}
const DropdownMenuLabel: Component<ComponentProps<"div"> & { inset?: boolean }> = (props) => {
const [local, others] = splitProps(props, ["class", "inset"])
return (
<div
class={cn("px-2 py-1.5 font-medium text-sm data-[inset]:pl-8", local.class)}
data-inset={local.inset}
data-slot="dropdown-menu-label"
{...others}
/>
)
}
type DropdownMenuSeparatorProps<T extends ValidComponent = "hr"> =
DropdownMenuPrimitive.DropdownMenuSeparatorProps<T> & {
class?: string | undefined
}
const DropdownMenuSeparator = <T extends ValidComponent = "hr">(
props: PolymorphicProps<T, DropdownMenuSeparatorProps<T>>
) => {
const [local, others] = splitProps(props as DropdownMenuSeparatorProps, ["class"])
return (
<DropdownMenuPrimitive.Separator
class={cn("-mx-1 my-1 h-px bg-border", local.class)}
data-slot="dropdown-menu-separator"
{...others}
/>
)
}
const DropdownMenuSub: Component<DropdownMenuPrimitive.DropdownMenuSubProps> = (props) => (
<DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
)
type DropdownMenuSubTriggerProps<T extends ValidComponent = "div"> =
DropdownMenuPrimitive.DropdownMenuSubTriggerProps<T> & {
class?: string | undefined
children?: JSX.Element
}
const DropdownMenuSubTrigger = <T extends ValidComponent = "div">(
props: PolymorphicProps<T, DropdownMenuSubTriggerProps<T>>
) => {
const [local, others] = splitProps(props as DropdownMenuSubTriggerProps, ["class", "children"])
return (
<DropdownMenuPrimitive.SubTrigger
class={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[expanded]:bg-accent data-[inset]:pl-8 data-[expanded]:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
local.class
)}
data-slot="dropdown-menu-sub-trigger"
{...others}
>
{local.children}
<svg
class="ml-auto size-4"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M9 6l6 6l-6 6" />
</svg>
</DropdownMenuPrimitive.SubTrigger>
)
}
type DropdownMenuSubContentProps<T extends ValidComponent = "div"> =
DropdownMenuPrimitive.DropdownMenuSubContentProps<T> & {
class?: string | undefined
}
const DropdownMenuSubContent = <T extends ValidComponent = "div">(
props: PolymorphicProps<T, DropdownMenuSubContentProps<T>>
) => {
const [local, others] = splitProps(props as DropdownMenuSubContentProps, ["class"])
return (
<DropdownMenuPrimitive.SubContent
class={cn(
"data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 z-50 min-w-[8rem] origin-(--kb-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[closed]:animate-out data-[expanded]:animate-in",
local.class
)}
data-slot="dropdown-menu-sub-content"
{...others}
/>
)
}
type DropdownMenuCheckboxItemProps<T extends ValidComponent = "div"> =
DropdownMenuPrimitive.DropdownMenuCheckboxItemProps<T> & {
class?: string | undefined
children?: JSX.Element
}
const DropdownMenuCheckboxItem = <T extends ValidComponent = "div">(
props: PolymorphicProps<T, DropdownMenuCheckboxItemProps<T>>
) => {
const [local, others] = splitProps(props as DropdownMenuCheckboxItemProps, ["class", "children"])
return (
<DropdownMenuPrimitive.CheckboxItem
class={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
local.class
)}
data-slot="dropdown-menu-checkbox-item"
{...others}
>
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<svg
class="size-4"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5 12l5 5l10 -10" />
</svg>
</DropdownMenuPrimitive.ItemIndicator>
</span>
{local.children}
</DropdownMenuPrimitive.CheckboxItem>
)
}
type DropdownMenuGroupLabelProps<T extends ValidComponent = "span"> =
DropdownMenuPrimitive.DropdownMenuGroupLabelProps<T> & {
class?: string | undefined
}
const DropdownMenuGroupLabel = <T extends ValidComponent = "span">(
props: PolymorphicProps<T, DropdownMenuGroupLabelProps<T>>
) => {
const [local, others] = splitProps(props as DropdownMenuGroupLabelProps, ["class"])
return (
<DropdownMenuPrimitive.GroupLabel
class={cn("px-2 py-1.5 font-semibold text-sm", local.class)}
data-slot="dropdown-menu-group-label"
{...others}
/>
)
}
type DropdownMenuRadioGroupProps<
T extends ValidComponent = "div",
TValue = string
> = DropdownMenuPrimitive.DropdownMenuRadioGroupProps<T, TValue>
const DropdownMenuRadioGroup = <T extends ValidComponent = "div", TValue = string>(
props: PolymorphicProps<T, DropdownMenuRadioGroupProps<T, TValue>>
) => <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
type DropdownMenuRadioItemProps<T extends ValidComponent = "div"> =
DropdownMenuPrimitive.DropdownMenuRadioItemProps<T> & {
class?: string | undefined
children?: JSX.Element
}
const DropdownMenuRadioItem = <T extends ValidComponent = "div">(
props: PolymorphicProps<T, DropdownMenuRadioItemProps<T>>
) => {
const [local, others] = splitProps(props as DropdownMenuRadioItemProps, ["class", "children"])
return (
<DropdownMenuPrimitive.RadioItem
class={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
local.class
)}
data-slot="dropdown-menu-radio-item"
{...others}
>
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<svg
class="size-2 fill-current"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
</svg>
</DropdownMenuPrimitive.ItemIndicator>
</span>
{local.children}
</DropdownMenuPrimitive.RadioItem>
)
}
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuPortal,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuShortcut,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuCheckboxItem,
DropdownMenuGroup,
DropdownMenuGroupLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem
}

Usage

import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
<DropdownMenu>
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuItem>Subscription</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

Examples

Checkboxes

Radio Group

Dialog

This example shows how to open a dialog from a dropdown menu.

Use modal={false} on the DropdownMenu component.

<DropdownMenu modal={false}>
<DropdownMenuTrigger as={Button<"button">} variant="outline">
Actions
</DropdownMenuTrigger>
</DropdownMenu>