Kosorikōsori alpha

Dropdown Menu

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

Installation

Install the primitive

Install the @radix-ui/react-dropdown-menu package.

npm install @radix-ui/react-dropdown-menu

Copy-paste the component

Copy and paste the component code in a .tsx file.

'use client';
 
import { forwardRef } from 'react';
import {
  CheckboxItem,
  Content,
  Group,
  Item,
  ItemIndicator,
  Label,
  Portal,
  RadioGroup,
  RadioItem,
  Root,
  Separator,
  Sub,
  SubContent,
  SubTrigger,
  Trigger,
} from '@radix-ui/react-dropdown-menu';
import {
  CheckIcon,
  ChevronRightIcon,
  DotFilledIcon,
} from '@radix-ui/react-icons';
import { clsx } from 'clsx';
import { tv } from 'tailwind-variants';
 
const dropdownMenuStyles = tv({
  slots: {
    content: clsx(
      'z-50 min-w-[8rem] overflow-hidden rounded-lg border bg-grey-base p-1 shadow-md',
      'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
      'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
      'data-[side=top]:slide-in-from-bottom-2',
      'data-[side=right]:slide-in-from-left-2',
      'data-[side=bottom]:slide-in-from-top-2',
      'data-[side=left]:slide-in-from-right-2',
    ),
    item: clsx(
      'group relative flex h-8 cursor-pointer select-none items-center rounded-md px-2 text-sm outline-none transition-colors',
      'focus:bg-primary-bg-hover',
      'active:bg-primary-bg-active',
      'data-[disabled]:cursor-not-allowed data-[disabled]:text-grey-solid',
    ),
    label:
      'flex h-8 select-none items-center px-2 text-xs font-medium text-grey-text',
    checkboxItem: clsx(
      'group relative flex h-8 select-none items-center rounded-md pl-8 pr-2 text-sm outline-none transition-colors',
      'focus:bg-primary-bg-hover',
      'active:bg-primary-bg-active',
      'data-[disabled]:cursor-not-allowed data-[disabled]:text-grey-solid',
    ),
    checkboxItemIndicator:
      'absolute left-2 flex size-3.5 items-center justify-center',
    checkboxItemIcon: 'size-4',
    radioItem: clsx(
      'group relative flex h-8 select-none items-center rounded-md pl-8 pr-2 text-sm outline-none transition-colors',
      'focus:bg-primary-bg-hover',
      'active:bg-primary-bg-active',
      'data-[disabled]:cursor-not-allowed data-[disabled]:text-grey-solid',
    ),
    radioItemIndicator:
      'absolute left-2 flex size-3.5 items-center justify-center',
    radioItemIcon: 'size-4',
    separator: '-mx-1 my-1 h-px bg-grey-line',
    subTrigger: clsx(
      'group flex h-8 cursor-pointer select-none items-center rounded-md px-2 text-sm outline-none transition-colors',
      'focus:bg-primary-bg-hover',
      'data-[state=open]:bg-primary-bg-hover',
      'data-[disabled]:cursor-not-allowed data-[disabled]:text-grey-solid',
    ),
    subTriggerIcon: clsx(
      'ml-auto size-4',
      'group-data-[disabled]:text-grey-solid',
    ),
    subContent: clsx(
      'z-50 min-w-[8rem] overflow-hidden rounded-lg border border-grey-line bg-grey-base p-1 shadow-md',
      'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
      'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
      'data-[side=top]:slide-in-from-bottom-2',
      'data-[side=right]:slide-in-from-left-2',
      'data-[side=bottom]:slide-in-from-top-2',
      'data-[side=left]:slide-in-from-right-2',
    ),
    shortcut: clsx(
      'ml-auto text-xs tracking-widest text-grey-text',
      'group-data-[disabled]:text-grey-solid',
    ),
  },
  variants: {
    inset: {
      true: {
        item: 'pl-8',
        label: 'pl-8',
        subTrigger: 'pl-8',
      },
    },
  },
});
 
const {
  content,
  item,
  label,
  checkboxItem,
  checkboxItemIndicator,
  checkboxItemIcon,
  radioItem,
  radioItemIndicator,
  radioItemIcon,
  separator,
  subTrigger,
  subTriggerIcon,
  subContent,
  shortcut,
} = dropdownMenuStyles();
 
/**
 * DropdownMenu component that provides a dropdown menu for user interactions.
 *
 * @param {React.ComponentPropsWithoutRef<typeof Root>} props - The props for the DropdownMenu component.
 *
 * @example
 * <DropdownMenu>
 *   <DropdownMenuTrigger>Open</DropdownMenuTrigger>
 *   <DropdownMenuContent>
 *     <DropdownMenuLabel>My Account</DropdownMenuLabel>
 *     <DropdownMenuSeparator />
 *     <DropdownMenuItem>Profile</DropdownMenuItem>
 *     <DropdownMenuItem>Billing</DropdownMenuItem>
 *     <DropdownMenuItem>Team</DropdownMenuItem>
 *     <DropdownMenuItem>Subscription</DropdownMenuItem>
 *   </DropdownMenuContent>
 * </DropdownMenu>
 *
 * @see {@link https://dub.sh/ui-dropdown-menu Dropdown Menu Docs} for further information.
 */
export const DropdownMenu = Root;
 
export const DropdownMenuTrigger = Trigger;
 
export const DropdownMenuPortal = Portal;
 
type DropdownMenuContentRef = React.ElementRef<typeof Content>;
type DropdownMenuContentProps = React.ComponentPropsWithoutRef<typeof Content>;
 
/**
 * DropdownMenuContent component that wraps the content of the DropdownMenu.
 *
 * @param {DropdownMenuContentProps} props - The props for the DropdownMenuContent component.
 *
 * @example
 * <DropdownMenuContent>
 *   <DropdownMenuItem>Profile</DropdownMenuItem>
 * </DropdownMenuContent>
 */
export const DropdownMenuContent = forwardRef<
  DropdownMenuContentRef,
  DropdownMenuContentProps
>(({ className, sideOffset = 4, ...props }, ref) => (
  <Content
    ref={ref}
    className={content({ className })}
    sideOffset={sideOffset}
    {...props}
  />
));
 
DropdownMenuContent.displayName = Content.displayName;
 
type DropdownMenuItemRef = React.ElementRef<typeof Item>;
type DropdownMenuItemProps = React.ComponentPropsWithoutRef<typeof Item> & {
  inset?: boolean;
};
 
/**
 * DropdownMenuItem component that represents a single item in the DropdownMenu.
 *
 * @param {DropdownMenuItemProps} props - The props for the DropdownMenuItem component.
 * @param {boolean} [inset] - Whether the item is inset.
 *
 * @example
 * <DropdownMenuItem>Profile</DropdownMenuItem>
 */
export const DropdownMenuItem = forwardRef<
  DropdownMenuItemRef,
  DropdownMenuItemProps
>(({ className, inset, ...props }, ref) => (
  <Item ref={ref} className={item({ className, inset })} {...props} />
));
 
DropdownMenuItem.displayName = Item.displayName;
 
export const DropdownMenuGroup = Group;
 
type DropdownMenuLabelRef = React.ElementRef<typeof Label>;
type DropdownMenuLabelProps = React.ComponentPropsWithoutRef<typeof Label> & {
  inset?: boolean;
};
 
/**
 * DropdownMenuLabel component that displays a label for a group of items in the DropdownMenu.
 *
 * @param {DropdownMenuLabelProps} props - The props for the DropdownMenuLabel component.
 * @param {boolean} [inset] - Whether the label is inset.
 *
 * @example
 * <DropdownMenuLabel>Settings</DropdownMenuLabel>
 */
export const DropdownMenuLabel = forwardRef<
  DropdownMenuLabelRef,
  DropdownMenuLabelProps
>(({ className, inset, ...props }, ref) => (
  <Label ref={ref} className={label({ className, inset })} {...props} />
));
 
DropdownMenuLabel.displayName = Label.displayName;
 
type DropdownMenuCheckboxItemRef = React.ElementRef<typeof CheckboxItem>;
type DropdownMenuCheckboxItemProps = React.ComponentPropsWithoutRef<
  typeof CheckboxItem
>;
 
/**
 * DropdownMenuCheckboxItem component that represents a checkbox item in the DropdownMenu.
 *
 * @param {DropdownMenuCheckboxItemProps} props - The props for the DropdownMenuCheckboxItem component.
 *
 * @example
 * <DropdownMenuCheckboxItem checked={true}>Enable Notifications</DropdownMenuCheckboxItem>
 */
export const DropdownMenuCheckboxItem = forwardRef<
  DropdownMenuCheckboxItemRef,
  DropdownMenuCheckboxItemProps
>(({ className, children, checked, ...props }, ref) => (
  <CheckboxItem
    ref={ref}
    checked={checked}
    className={checkboxItem({ className })}
    {...props}
  >
    <span className={checkboxItemIndicator()}>
      <ItemIndicator>
        <CheckIcon className={checkboxItemIcon()} />
      </ItemIndicator>
    </span>
    {children}
  </CheckboxItem>
));
 
DropdownMenuCheckboxItem.displayName = CheckboxItem.displayName;
 
export const DropdownMenuRadioGroup = RadioGroup;
 
type DropdownMenuRadioItemRef = React.ElementRef<typeof RadioItem>;
type DropdownMenuRadioItemProps = React.ComponentPropsWithoutRef<
  typeof RadioItem
>;
 
/**
 * DropdownMenuRadioItem component that represents a radio item in the DropdownMenu.
 *
 * @param {DropdownMenuRadioItemProps} props - The props for the DropdownMenuRadioItem component.
 *
 * @example
 * <DropdownMenuRadioItem>Option 1</DropdownMenuRadioItem>
 */
export const DropdownMenuRadioItem = forwardRef<
  DropdownMenuRadioItemRef,
  DropdownMenuRadioItemProps
>(({ className, children, ...props }, ref) => (
  <RadioItem ref={ref} className={radioItem({ className })} {...props}>
    <span className={radioItemIndicator()}>
      <ItemIndicator>
        <DotFilledIcon className={radioItemIcon()} />
      </ItemIndicator>
    </span>
    {children}
  </RadioItem>
));
 
DropdownMenuRadioItem.displayName = RadioItem.displayName;
 
type DropdownMenuSeparatorRef = React.ElementRef<typeof Separator>;
type DropdownMenuSeparatorProps = React.ComponentPropsWithoutRef<
  typeof Separator
>;
 
/**
 * DropdownMenuSeparator component that visually separates items in the DropdownMenu.
 *
 * @param {DropdownMenuSeparatorProps} props - The props for the DropdownMenuSeparator component.
 *
 * @example
 * <DropdownMenuSeparator />
 */
export const DropdownMenuSeparator = forwardRef<
  DropdownMenuSeparatorRef,
  DropdownMenuSeparatorProps
>(({ className, ...props }, ref) => (
  <Separator ref={ref} className={separator({ className })} {...props} />
));
 
DropdownMenuSeparator.displayName = Separator.displayName;
 
export const DropdownMenuSub = Sub;
 
type DropdownMenuSubTriggerRef = React.ElementRef<typeof SubTrigger>;
type DropdownMenuSubTriggerProps = React.ComponentPropsWithoutRef<
  typeof SubTrigger
> & {
  inset?: boolean;
};
 
/**
 * DropdownMenuSubTrigger component that serves as the trigger for a sub-menu in the DropdownMenu.
 *
 * @param {DropdownMenuSubTriggerProps} props - The props for the DropdownMenuSubTrigger component.
 * @param {boolean} [inset] - Whether the trigger is inset.
 *
 * @example
 * <DropdownMenuSubTrigger>More Options</DropdownMenuSubTrigger>
 */
export const DropdownMenuSubTrigger = forwardRef<
  DropdownMenuSubTriggerRef,
  DropdownMenuSubTriggerProps
>(({ className, inset, children, ...props }, ref) => (
  <SubTrigger ref={ref} className={subTrigger({ className, inset })} {...props}>
    {children}
    <ChevronRightIcon className={subTriggerIcon()} />
  </SubTrigger>
));
 
DropdownMenuSubTrigger.displayName = SubTrigger.displayName;
 
type DropdownMenuSubContentRef = React.ElementRef<typeof SubContent>;
type DropdownMenuSubContentProps = React.ComponentPropsWithoutRef<
  typeof SubContent
>;
 
/**
 * DropdownMenuSubContent component that wraps the content of a sub-menu in the DropdownMenu.
 *
 * @param {DropdownMenuSubContentProps} props - The props for the DropdownMenuSubContent component.
 *
 * @example
 * <DropdownMenuSubContent>...</DropdownMenuSubContent>
 */
export const DropdownMenuSubContent = forwardRef<
  DropdownMenuSubContentRef,
  DropdownMenuSubContentProps
>(({ className, ...props }, ref) => (
  <SubContent ref={ref} className={subContent({ className })} {...props} />
));
 
DropdownMenuSubContent.displayName = SubContent.displayName;
 
type DropdownMenuShortcutProps = React.HTMLAttributes<HTMLSpanElement>;
 
/**
 * DropdownMenuShortcut component that displays a keyboard shortcut for a command item.
 *
 * @param {DropdownMenuShortcutProps} props - The props for the DropdownMenuShortcut component.
 *
 * @example
 * <DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
 */
export const DropdownMenuShortcut = ({
  className,
  ...props
}: DropdownMenuShortcutProps) => (
  <span className={shortcut({ className })} {...props} />
);
 
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';

Update import paths

Update the @kosori/ui import paths to fit your project structure, for example, using ~/components/ui.

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

On this page