Skip to content
portalbits
Home Docs GitHub(opens in a new tab)

Guides

Get startedFoundationsMotion

Components

All components

Buttons

ButtonIconButton

Inputs

InputSelectTextareaSlider

Selection

CheckboxToggleToggleGroupSwitchRadioGroup

Identity

AvatarBadge

Overlays

DialogPopoverTooltipDropdownMenuAlertDialog

Disclosure

TabsAccordion

Cards

AppCardArticleCardFeatureCard

Content

CarouselSectionHeaderEmptyStateAlertProgressSeparatorRowListRow

Chrome

PortalShellSidebarNavMobileNavTopBarAuthFrameColorSelectorFloatingNav

DropdownMenu

A list of actions behind one trigger, with typeahead and arrow keys.

Last action: Nothing yet

Variations

Source

<script lang="ts">
  import 'portal-bits/styles.css';
  import 'portal-bits/tokens.css';
  import type { Snippet } from 'svelte';

  let { children }: { children: Snippet } = $props();
</script>

{@render children()}
<script lang="ts">
  import DropdownMenuExample from './DropdownMenuExample.svelte';
</script>

<DropdownMenuExample align={"end"} shortcuts={true} />
<script lang="ts">
  import { DropdownMenu } from 'portal-bits';
  import { Copy, Ellipsis, Pencil, Share2, Trash2 } from '@lucide/svelte';
  let {
    align = 'end',
    shortcuts = true,
  }: { align?: 'start' | 'center' | 'end'; shortcuts?: boolean } = $props();
  let last = $state('Nothing yet');
</script>

<div class="stack">
  <DropdownMenu
    label="Draft actions"
    {align}
    items={[
      {
        label: 'Rename',
        icon: Pencil,
        shortcut: shortcuts ? 'R' : undefined,
        onSelect: () => (last = 'Rename'),
      },
      {
        label: 'Duplicate',
        icon: Copy,
        shortcut: shortcuts ? 'D' : undefined,
        onSelect: () => (last = 'Duplicate'),
      },
      { label: 'Share', icon: Share2, onSelect: () => (last = 'Share') },
      { type: 'separator' },
      { label: 'Delete', icon: Trash2, tone: 'danger', onSelect: () => (last = 'Delete') },
    ]}
  >
    {#snippet trigger()}<Ellipsis size={17} />{/snippet}
  </DropdownMenu>
  <p aria-live="polite">Last action: {last}</p>
</div>

<style>
  .stack {
    display: grid;
    justify-items: center;
    gap: 12px;
  }
  p {
    margin: 0;
  }
</style>

API reference

These tables list the props and snippets that DropdownMenu declares. Attributes passed through to an element or primitive are listed under Forwarded attributes, not here.

Props

NameTypeDefaultRequiredBindingDescription
labelstringNoneYesOne-wayNames the trigger with `aria-label`.
itemsMenuEntry[]NoneYesOne-wayItems as `{ label, onSelect?, icon?, shortcut?, tone?, disabled? }`, or `{ type: 'separator' }`.
openbooleanfalseNoBindableWhether the menu is open.
align'start' | 'center' | 'end'endNoOne-wayAlignment against the trigger.
side'top' | 'right' | 'bottom' | 'left'bottomNoOne-wayPreferred side of the trigger.
theme'light' | 'dark'lightNoOne-wayTheme scope for the portaled content. Portaled content does not inherit the page scope.
triggerClassstringSecondary 40px icon buttonNoOne-wayReplaces the trigger classes.

Snippets

NameTypeDefaultRequiredBindingDescription
triggerSnippetNoneYesOne-wayThe trigger content, usually an icon.

Forwarded attributes

None. DropdownMenu forwards no attributes.

The primitive underneath is a Bits UI peer dependency. Its own options are documented in the Bits UI Dropdown Menu documentation. Only the props listed on this page are part of the DropdownMenu contract. No data attributes are documented as stable.

Limits

  • Items are actions. Checkbox items, radio items, and submenus are not supported.
  • `shortcut` is a visible hint only. The menu does not bind the key.
  • The item rows and danger tone are an adaptation of the popover surface.

Related

  • Popover
  • AlertDialog
Previous Tooltip Next AlertDialog

On this page

  • Preview
  • Source
  • API reference
  • Props
  • Snippets
  • Forwarded attributes
  • Limits