Dropdown Menu
Displays a menu of items that users can select from when triggered.
<script lang="ts">
import { Avatar, DropdownMenu } from "bits-ui";
import Cardholder from "phosphor-svelte/lib/Cardholder";
import CaretRight from "phosphor-svelte/lib/CaretRight";
import DotsThree from "phosphor-svelte/lib/DotsThree";
import GearSix from "phosphor-svelte/lib/GearSix";
import UserCircle from "phosphor-svelte/lib/UserCircle";
import UserCirclePlus from "phosphor-svelte/lib/UserCirclePlus";
import Bell from "phosphor-svelte/lib/Bell";
import Check from "phosphor-svelte/lib/Check";
import DotOutline from "phosphor-svelte/lib/DotOutline";
let notifications = $state<boolean>(false);
let invited = $state("");
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger
class="border-input text-foreground shadow-btn hover:bg-muted inline-flex h-10 w-10 select-none items-center justify-center rounded-full border text-sm font-medium active:scale-[0.98]"
>
<DotsThree class="text-foreground h-6 w-6" />
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
class="border-muted bg-background shadow-popover outline-hidden focus-visible:outline-hidden w-[229px] rounded-xl border px-1 py-1.5"
sideOffset={8}
>
<DropdownMenu.Item
class="rounded-button data-highlighted:bg-muted ring-0! ring-transparent! flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
<div class="flex items-center">
<UserCircle class="text-foreground-alt mr-2 size-5" />
Profile
</div>
<div class="ml-auto flex items-center gap-px">
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-xs"
>
⌘
</kbd>
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-[10px]"
>
P
</kbd>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
class="rounded-button data-highlighted:bg-muted ring-0! ring-transparent! flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
<div class="flex items-center">
<Cardholder class="text-foreground-alt mr-2 size-5" />
Billing
</div>
<div class="ml-auto flex items-center gap-px">
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-xs"
>
⌘
</kbd>
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-[10px]"
>
B
</kbd>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
class="rounded-button data-highlighted:bg-muted ring-0! ring-transparent! flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
<div class="flex items-center">
<GearSix class="text-foreground-alt mr-2 size-5" />
Settings
</div>
<div class="ml-auto flex items-center gap-px">
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-xs"
>
⌘
</kbd>
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-[10px]"
>
S
</kbd>
</div>
</DropdownMenu.Item>
<DropdownMenu.CheckboxItem
bind:checked={notifications}
class="rounded-button data-highlighted:bg-muted ring-0! ring-transparent! flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<div class="flex items-center pr-4">
<Bell class="text-foreground-alt mr-2 size-5" />
Notifications
</div>
<div class="ml-auto flex items-center gap-px">
{#if checked}
<Check class="size-4" />
{/if}
</div>
{/snippet}
</DropdownMenu.CheckboxItem>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger
class="rounded-button data-highlighted:bg-muted data-[state=open]:bg-muted ring-0! ring-transparent! flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
<div class="flex items-center">
<UserCirclePlus class="text-foreground-alt mr-2 size-5" />
Workspace
</div>
<div class="ml-auto flex items-center gap-px">
<CaretRight class="text-foreground-alt size-5" />
</div>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent
class="border-muted bg-background shadow-popover ring-0! ring-transparent! w-[209px] rounded-xl border px-1 py-1.5"
sideOffset={10}
>
<DropdownMenu.RadioGroup bind:value={invited}>
<DropdownMenu.RadioItem
value="huntabyte"
class="rounded-button data-highlighted:bg-muted ring-0! ring-transparent! flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<Avatar.Root
class="border-foreground/50 relative mr-3 flex size-5 shrink-0 overflow-hidden rounded-full border"
>
<Avatar.Image
src="https://github.com/huntabyte.png"
alt="@huntabyte"
class="aspect-square h-full w-full"
/>
<Avatar.Fallback
class="bg-muted text-xxs flex h-full w-full items-center justify-center rounded-full"
>HJ</Avatar.Fallback
>
</Avatar.Root>
@huntabyte
{#if checked}
<DotOutline class="ml-auto size-4" />
{/if}
{/snippet}
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem
value="pavel"
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<Avatar.Root
class="border-foreground/50 relative mr-3 flex size-5 shrink-0 overflow-hidden rounded-full border"
>
<Avatar.Image
src="https://github.com/pavelstianko.png"
alt="@pavel_stianko"
class="aspect-square h-full w-full"
/>
<Avatar.Fallback
class="bg-muted flex h-full w-full items-center justify-center rounded-full text-xs"
>PS</Avatar.Fallback
>
</Avatar.Root>
@pavel_stianko
{#if checked}
<DotOutline class="ml-auto size-4" />
{/if}
{/snippet}
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem
value="cokakoala"
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<Avatar.Root
class="border-foreground/50 relative mr-3 flex size-5 shrink-0 overflow-hidden rounded-full border"
>
<Avatar.Image
src="https://github.com/adriangonz97.png"
alt="@cokakoala_"
class="aspect-square h-full w-full"
/>
<Avatar.Fallback
class="bg-muted flex h-full w-full items-center justify-center rounded-full text-xs"
>CK</Avatar.Fallback
>
</Avatar.Root>
@cokakoala_
{#if checked}
<DotOutline class="ml-auto size-4" />
{/if}
{/snippet}
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem
value="tglide"
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<Avatar.Root
class="border-foreground/50 relative mr-3 flex size-5 shrink-0 overflow-hidden rounded-full border"
>
<Avatar.Image
src="https://github.com/tglide.png"
alt="@tglide"
class="aspect-square h-full w-full"
/>
<Avatar.Fallback
class="bg-muted flex h-full w-full items-center justify-center rounded-full text-xs"
>
TL
</Avatar.Fallback>
</Avatar.Root>
@thomasglopes
{#if checked}
<DotOutline class="ml-auto size-4" />
{/if}
{/snippet}
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
@import "tailwindcss";
@plugin "tailwindcss-animate";
@custom-variant dark (&:is(.dark *));
@font-face {
font-family: "Cal Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/CalSans-SemiBold.woff2") format("woff2");
}
:root {
/* Colors */
--background: hsl(0 0% 100%);
--background-alt: hsl(0 0% 100%);
--foreground: hsl(0 0% 9%);
--foreground-alt: hsl(0 0% 32%);
--muted: hsl(240 5% 96%);
--muted-foreground: hsla(0 0% 9% / 0.4);
--border: hsl(240 6% 10%);
--border-input: hsla(240 6% 10% / 0.17);
--border-input-hover: hsla(240 6% 10% / 0.4);
--border-card: hsla(240 6% 10% / 0.1);
--dark: hsl(240 6% 10%);
--dark-10: hsla(240 6% 10% / 0.1);
--dark-40: hsla(240 6% 10% / 0.4);
--dark-04: hsla(240 6% 10% / 0.04);
--accent: hsl(204 94% 94%);
--accent-foreground: hsl(204 80% 16%);
--destructive: hsl(347 77% 50%);
--tertiary: hsl(37.7 92.1% 50.2%);
--line: hsl(0 0% 100%);
/* black */
--contrast: hsl(0 0% 0%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.04) inset;
--shadow-popover: 0px 7px 12px 3px hsla(var(--dark-10));
--shadow-kbd: 0px 2px 0px 0px rgba(0, 0, 0, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.03);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(24, 24, 27, 0.17);
}
.dark {
/* Colors */
--background: hsl(0 0% 5%);
--background-alt: hsl(0 0% 8%);
--foreground: hsl(0 0% 95%);
--foreground-alt: hsl(0 0% 70%);
--muted: hsl(240 4% 16%);
--muted-foreground: hsla(0 0% 100% / 0.4);
--border: hsl(0 0% 96%);
--border-input: hsla(0 0% 96% / 0.17);
--border-input-hover: hsla(0 0% 96% / 0.4);
--border-card: hsla(0 0% 96% / 0.1);
--dark: hsl(0 0% 96%);
--dark-40: hsl(0 0% 96% / 0.4);
--dark-10: hsl(0 0% 96% / 0.1);
--dark-04: hsl(0 0% 96% / 0.04);
--accent: hsl(204 90% 90%);
--accent-foreground: hsl(204 94% 94%);
--destructive: hsl(350 89% 60%);
--line: hsl(0 0% 9.02%);
--tertiary: hsl(61.3 100% 82.2%);
/* white */
--contrast: hsl(0 0% 100%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.3);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.5) inset;
--shadow-popover: 0px 7px 12px 3px hsla(0deg 0% 0% / 30%);
--shadow-kbd: 0px 2px 0px 0px rgba(255, 255, 255, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.2);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.4);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(244, 244, 245, 0.1);
}
@theme inline {
--color-background: var(--background);
--color-background-alt: var(--background-alt);
--color-foreground: var(--foreground);
--color-foreground-alt: var(--foreground-alt);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-border: var(--border-card);
--color-border-input: var(--border-input);
--color-border-input-hover: var(--border-input-hover);
--color-border-card: var(--border-card);
--color-dark: var(--dark);
--color-dark-10: var(--dark-10);
--color-dark-40: var(--dark-40);
--color-dark-04: var(--dark-04);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-tertiary: var(--tertiary);
--color-line: var(--line);
--color-contrast: var(--contrast);
--shadow-mini: var(--shadow-mini);
--shadow-mini-inset: var(--shadow-mini-inset);
--shadow-popover: var(--shadow-popover);
--shadow-kbd: var(--shadow-kbd);
--shadow-btn: var(--shadow-btn);
--shadow-card: var(--shadow-card);
--shadow-date-field-focus: var(--shadow-date-field-focus);
--text-xxs: 10px;
--radius-card: 16px;
--radius-card-lg: 20px;
--radius-card-sm: 10px;
--radius-input: 9px;
--radius-button: 5px;
--radius-5px: 5px;
--radius-9px: 9px;
--radius-10px: 10px;
--radius-15px: 15px;
--spacing-input: 3rem;
--spacing-input-sm: 2.5rem;
--breakpoint-desktop: 1440px;
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-caret-blink: caret-blink 1s ease-out infinite;
--animate-scale-in: scale-in 0.2s ease;
--animate-scale-out: scale-out 0.15s ease;
--animate-fade-in: fade-in 0.2s ease;
--animate-fade-out: fade-out 0.15s ease;
--animate-enter-from-left: enter-from-left 0.2s ease;
--animate-enter-from-right: enter-from-right 0.2s ease;
--animate-exit-to-left: exit-to-left 0.2s ease;
--animate-exit-to-right: exit-to-right 0.2s ease;
--font-sans: "Inter", "sans-serif";
--font-mono: "Source Code Pro", "monospace";
--font-alt: "Courier", "sans-serif";
--font-display: "Cal Sans", "sans-serif";
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--bits-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--bits-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes caret-blink {
0%,
70%,
100% {
opacity: 1;
}
20%,
50% {
opacity: 0;
}
}
@keyframes enter-from-right {
from {
opacity: 0;
transform: translateX(200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes enter-from-left {
from {
opacity: 0;
transform: translateX(-200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes exit-to-right {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(200px);
}
}
@keyframes exit-to-left {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-200px);
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: rotateX(-10deg) scale(0.9);
}
to {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
}
@keyframes scale-out {
from {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
to {
opacity: 0;
transform: rotateX(-10deg) scale(0.95);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-border-card, currentColor);
}
* {
@apply border-border;
}
html {
-webkit-text-size-adjust: 100%;
font-variation-settings: normal;
scrollbar-color: var(--bg-muted);
}
body {
@apply bg-background text-foreground;
font-feature-settings:
"rlig" 1,
"calt" 1;
}
::selection {
background: #fdffa4;
color: black;
}
}
@layer components {
*:not(body):not(.focus-override) {
outline: none !important;
&:focus-visible {
@apply focus-visible:ring-foreground focus-visible:ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1;
}
}
.link {
@apply hover:text-foreground/80 focus-visible:ring-foreground focus-visible:ring-offset-background rounded-xs focus-visible:outline-hidden inline-flex items-center gap-1 font-medium underline underline-offset-4 focus-visible:ring-2 focus-visible:ring-offset-2;
}
}
Structure
<script lang="ts">
import { DropdownMenu } from "bits-ui";
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger />
<DropdownMenu.Content>
<DropdownMenu.Group>
<DropdownMenu.GroupHeading />
<DropdownMenu.Item />
</DropdownMenu.Group>
<DropdownMenu.Group>
<DropdownMenu.Item />
</DropdownMenu.Group>
<DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxIndicator />
</DropdownMenu.CheckboxItem>
<DropdownMenu.RadioGroup>
<DropdownMenu.RadioItem>
<DropdownMenu.RadioIndicator />
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger />
<DropdownMenu.SubContent />
</DropdownMenu.Sub>
<DropdownMenu.Separator />
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Root>
Reusable Components
If you're planning to use Dropdown Menu in multiple places, you can create a reusable component that wraps the Dropdown Menu component.
This example shows you how to create a Dropdown Menu component that accepts a few custom props that make it more capable.
<script lang="ts">
import type { Snippet } from "svelte";
import { DropdownMenu, type WithoutChild } from "bits-ui";
type Props = DropdownMenu.Props & {
buttonText: string;
items: string[];
contentProps?: WithoutChild<DropdownMenu.Content.Props>;
// other component props if needed
};
let {
open = $bindable(false),
children,
buttonText,
items,
contentProps,
...restProps
}: Props = $props();
</script>
<DropdownMenu.Root bind:open {...restProps}>
<DropdownMenu.Trigger>
{buttonText}
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content {...contentProps}>
<DropdownMenu.Group aria-label={buttonText}>
{#each items as item}
<DropdownMenu.Item textValue={item}>
{item}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
You can then use the MyDropdownMenu
component like this:
<script lang="ts">
import MyDropdownMenu from "./MyDropdownMenu.svelte";
</script>
<MyDropdownMenu
buttonText="Select a manager"
items={["Michael Scott", "Dwight Schrute", "Jim Halpert"]}
/>
Managing Open State
This section covers how to manage the open
state of the menu.
Two-Way Binding
Use bind:open
for simple, automatic state synchronization:
<script lang="ts">
import { DropdownMenu } from "bits-ui";
let isOpen = $state(false);
</script>
<button onclick={() => (isOpen = true)}>Open Context Menu</button>
<DropdownMenu.Root bind:open={isOpen}>
<!-- ... -->
</DropdownMenu.Root>
Fully Controlled
Use a Function Binding for complete control over the state's reads and writes.
<script lang="ts">
import { DropdownMenu } from "bits-ui";
let myOpen = $state(false);
function getOpen() {
return myOpen;
}
function setOpen(newOpen: boolean) {
myOpen = newOpen;
}
</script>
<DropdownMenu.Root bind:open={getOpen, setOpen}>
<!-- ... -->
</DropdownMenu.Root>
Groups
To group related menu items, you can use the DropdownMenu.Group
component along with either a DropdownMenu.GroupHeading
or an aria-label
attribute on the DropdownMenu.Group
component.
<DropdownMenu.Group>
<DropdownMenu.GroupHeading>File</DropdownMenu.GroupHeading>
<DropdownMenu.Item>New</DropdownMenu.Item>
<DropdownMenu.Item>Open</DropdownMenu.Item>
<DropdownMenu.Item>Save</DropdownMenu.Item>
<DropdownMenu.Item>Save As</DropdownMenu.Item>
</DropdownMenu.Group>
<!-- or -->
<DropdownMenu.Group aria-label="file">
<DropdownMenu.Item>New</DropdownMenu.Item>
<DropdownMenu.Item>Open</DropdownMenu.Item>
<DropdownMenu.Item>Save</DropdownMenu.Item>
<DropdownMenu.Item>Save As</DropdownMenu.Item>
</DropdownMenu.Group>
Group Heading
The DropdownMenu.GroupHeading
component must be a child of either a DropdownMenu.Group
or DropdownMenu.RadioGroup
component. If used on its own, an error will be thrown during development.
<DropdownMenu.Group>
<DropdownMenu.GroupHeading>File</DropdownMenu.GroupHeading>
<!-- ... items here -->
</DropdownMenu.Group>
<!-- or -->
<DropdownMenu.RadioGroup>
<DropdownMenu.GroupHeading>Favorite color</DropdownMenu.GroupHeading>
<!-- ... radio items here -->
</DropdownMenu.RadioGroup>
Radio Groups
You can combine the DropdownMenu.RadioGroup
and DropdownMenu.RadioItem
components to create a radio group within a menu.
<script lang="ts">
import { DropdownMenu } from "bits-ui";
const values = ["one", "two", "three"];
let value = $state("one");
</script>
<DropdownMenu.RadioGroup bind:value>
<DropdownMenu.GroupHeading>Favorite number</DropdownMenu.GroupHeading>
{#each values as value}
<DropdownMenu.RadioItem {value}>
{#snippet children({ checked })}
{#if checked}
✅
{/if}
{value}
{/snippet}
</DropdownMenu.RadioItem>
{/each}
</DropdownMenu.RadioGroup>
The value
state does not persist between menu open/close cycles. To persist the state, you must store it in a $state
variable and pass it to the value
prop.
Checkbox Items
You can use the DropdownMenu.CheckboxItem
component to create a menuitemcheckbox
element to add checkbox functionality to menu items.
<script lang="ts">
import { DropdownMenu } from "bits-ui";
let notifications = $state(true);
</script>
<DropdownMenu.CheckboxItem bind:checked={notifications}>
{#snippet children({ checked, indeterminate })}
{#if indeterminate}
-
{:else if checked}
✅
{/if}
Notifications
{/snippet}
</DropdownMenu.CheckboxItem>
The checked
state does not persist between menu open/close cycles. To persist the state, you must store it in a $state
variable and pass it to the checked
prop.
Checkbox Groups
You can use the DropdownMenu.CheckboxGroup
component around a set of DropdownMenu.CheckboxItem
components to create a checkbox group within a menu, where the value
prop is an array of the selected values.
<script lang="ts">
import { DropdownMenu } from "bits-ui";
let colors = $state<string[]>([]);
</script>
<DropdownMenu.CheckboxGroup bind:value={colors}>
<DropdownMenu.GroupHeading>Favorite color</DropdownMenu.GroupHeading>
<DropdownMenu.CheckboxItem value="red">
{#snippet children({ checked })}
{#if checked}
✅
{/if}
Red
{/snippet}
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem value="blue">
{#snippet children({ checked })}
{#if checked}
✅
{/if}
Blue
{/snippet}
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem value="green">
{#snippet children({ checked })}
{#if checked}
✅
{/if}
Green
{/snippet}
</DropdownMenu.CheckboxItem>
</DropdownMenu.CheckboxGroup>
The value
state does not persist between menu open/close cycles. To persist the state, you must store it in a $state
variable and pass it to the value
prop.
Nested Menus
You can create nested menus using the DropdownMenu.Sub
component to create complex menu structures.
<script lang="ts">
import { DropdownMenu } from "bits-ui";
</script>
<DropdownMenu.Content>
<DropdownMenu.Item>Item 1</DropdownMenu.Item>
<DropdownMenu.Item>Item 2</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Open Sub Menu</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Item>Sub Item 1</DropdownMenu.Item>
<DropdownMenu.Item>Sub Item 2</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</DropdownMenu.Content>
Svelte Transitions
You can use the forceMount
prop along with the child
snippet to forcefully mount the DropdownMenu.Content
component to use Svelte Transitions or another animation library that requires more control.
<script lang="ts">
import { DropdownMenu } from "bits-ui";
import { fly } from "svelte/transition";
</script>
<DropdownMenu.Content forceMount>
{#snippet child({ wrapperProps, props, open })}
{#if open}
<div {...wrapperProps}>
<div {...props} transition:fly>
<DropdownMenu.Item>Item 1</DropdownMenu.Item>
<DropdownMenu.Item>Item 2</DropdownMenu.Item>
</div>
</div>
{/if}
{/snippet}
</DropdownMenu.Content>
Of course, this isn't the prettiest syntax, so it's recommended to create your own reusable content component that handles this logic if you intend to use this approach. For more information on using transitions with Bits UI components, see the Transitions documentation.
<script lang="ts">
import { Avatar, DropdownMenu } from "bits-ui";
import Cardholder from "phosphor-svelte/lib/Cardholder";
import CaretRight from "phosphor-svelte/lib/CaretRight";
import DotsThree from "phosphor-svelte/lib/DotsThree";
import GearSix from "phosphor-svelte/lib/GearSix";
import UserCircle from "phosphor-svelte/lib/UserCircle";
import UserCirclePlus from "phosphor-svelte/lib/UserCirclePlus";
import Bell from "phosphor-svelte/lib/Bell";
import Check from "phosphor-svelte/lib/Check";
import DotOutline from "phosphor-svelte/lib/DotOutline";
import { fly } from "svelte/transition";
let notifications = $state<boolean>(false);
let invited = $state("");
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger
class="border-input shadow-btn hover:bg-muted inline-flex h-10 w-10 select-none items-center justify-center rounded-full border text-sm font-medium active:scale-[0.98]"
>
<DotsThree class="text-foreground h-6 w-6" />
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
class="border-muted bg-background shadow-popover w-[229px] rounded-xl border px-1 py-1.5 focus-visible:outline-none"
sideOffset={8}
forceMount
>
{#snippet child({ wrapperProps, props, open })}
{#if open}
<div {...wrapperProps}>
<div {...props} transition:fly={{ duration: 300 }}>
<DropdownMenu.Item
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
<div class="flex items-center">
<UserCircle class="text-foreground-alt mr-2 size-5" />
Profile
</div>
<div class="ml-auto flex items-center gap-px">
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-xs"
>
⌘
</kbd>
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-[10px]"
>
P
</kbd>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
<div class="flex items-center">
<Cardholder class="text-foreground-alt mr-2 size-5" />
Billing
</div>
<div class="ml-auto flex items-center gap-px">
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-xs"
>
⌘
</kbd>
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-[10px]"
>
B
</kbd>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
<div class="flex items-center">
<GearSix class="text-foreground-alt mr-2 size-5" />
Settings
</div>
<div class="ml-auto flex items-center gap-px">
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-xs"
>
⌘
</kbd>
<kbd
class="rounded-button border-dark-10 bg-background-alt text-muted-foreground shadow-kbd inline-flex size-5 items-center justify-center border text-[10px]"
>
S
</kbd>
</div>
</DropdownMenu.Item>
<DropdownMenu.CheckboxItem
bind:checked={notifications}
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<div class="flex items-center pr-4">
<Bell class="text-foreground-alt mr-2 size-5" />
Notifications
</div>
<div class="ml-auto flex items-center gap-px">
{#if checked}
<Check class="size-4" />
{/if}
</div>
{/snippet}
</DropdownMenu.CheckboxItem>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger
class="rounded-button data-highlighted:bg-muted data-[state=open]:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
<div class="flex items-center">
<UserCirclePlus class="text-foreground-alt mr-2 size-5" />
Workspace
</div>
<div class="ml-auto flex items-center gap-px">
<CaretRight class="text-foreground-alt size-5" />
</div>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent
class="border-muted bg-background shadow-popover w-[209px] rounded-xl border px-1 py-1.5 focus-visible:outline-none"
sideOffset={10}
>
<DropdownMenu.RadioGroup bind:value={invited}>
<DropdownMenu.RadioItem
value="huntabyte"
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<Avatar.Root
class="border-foreground/50 relative mr-3 flex size-5 shrink-0 overflow-hidden rounded-full border"
>
<Avatar.Image
src="https://github.com/huntabyte.png"
alt="@huntabyte"
class="aspect-square h-full w-full"
/>
<Avatar.Fallback
class="bg-muted text-xxs flex h-full w-full items-center justify-center rounded-full"
>HJ</Avatar.Fallback
>
</Avatar.Root>
@huntabyte
{#if checked}
<DotOutline class="ml-auto size-4" />
{/if}
{/snippet}
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem
value="pavel"
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<Avatar.Root
class="border-foreground/50 relative mr-3 flex size-5 shrink-0 overflow-hidden rounded-full border"
>
<Avatar.Image
src="https://github.com/pavelstianko.png"
alt="@pavel_stianko"
class="aspect-square h-full w-full"
/>
<Avatar.Fallback
class="bg-muted flex h-full w-full items-center justify-center rounded-full text-xs"
>PS</Avatar.Fallback
>
</Avatar.Root>
@pavel_stianko
{#if checked}
<DotOutline class="ml-auto size-4" />
{/if}
{/snippet}
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem
value="cokakoala"
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<Avatar.Root
class="border-foreground/50 relative mr-3 flex size-5 shrink-0 overflow-hidden rounded-full border"
>
<Avatar.Image
src="https://github.com/adriangonz97.png"
alt="@cokakoala_"
class="aspect-square h-full w-full"
/>
<Avatar.Fallback
class="bg-muted flex h-full w-full items-center justify-center rounded-full text-xs"
>CK</Avatar.Fallback
>
</Avatar.Root>
@cokakoala_
{#if checked}
<DotOutline class="ml-auto size-4" />
{/if}
{/snippet}
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem
value="tglide"
class="rounded-button data-highlighted:bg-muted flex h-10 select-none items-center py-3 pl-3 pr-1.5 text-sm font-medium focus-visible:outline-none"
>
{#snippet children({ checked })}
<Avatar.Root
class="border-foreground/50 relative mr-3 flex size-5 shrink-0 overflow-hidden rounded-full border"
>
<Avatar.Image
src="https://github.com/tglide.png"
alt="@tglide"
class="aspect-square h-full w-full"
/>
<Avatar.Fallback
class="bg-muted flex h-full w-full items-center justify-center rounded-full text-xs"
>
TL
</Avatar.Fallback>
</Avatar.Root>
@thomasglopes
{#if checked}
<DotOutline class="ml-auto size-4" />
{/if}
{/snippet}
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</div>
</div>
{/if}
{/snippet}
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
@import "tailwindcss";
@plugin "tailwindcss-animate";
@custom-variant dark (&:is(.dark *));
@font-face {
font-family: "Cal Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/CalSans-SemiBold.woff2") format("woff2");
}
:root {
/* Colors */
--background: hsl(0 0% 100%);
--background-alt: hsl(0 0% 100%);
--foreground: hsl(0 0% 9%);
--foreground-alt: hsl(0 0% 32%);
--muted: hsl(240 5% 96%);
--muted-foreground: hsla(0 0% 9% / 0.4);
--border: hsl(240 6% 10%);
--border-input: hsla(240 6% 10% / 0.17);
--border-input-hover: hsla(240 6% 10% / 0.4);
--border-card: hsla(240 6% 10% / 0.1);
--dark: hsl(240 6% 10%);
--dark-10: hsla(240 6% 10% / 0.1);
--dark-40: hsla(240 6% 10% / 0.4);
--dark-04: hsla(240 6% 10% / 0.04);
--accent: hsl(204 94% 94%);
--accent-foreground: hsl(204 80% 16%);
--destructive: hsl(347 77% 50%);
--tertiary: hsl(37.7 92.1% 50.2%);
--line: hsl(0 0% 100%);
/* black */
--contrast: hsl(0 0% 0%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.04) inset;
--shadow-popover: 0px 7px 12px 3px hsla(var(--dark-10));
--shadow-kbd: 0px 2px 0px 0px rgba(0, 0, 0, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.03);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(24, 24, 27, 0.17);
}
.dark {
/* Colors */
--background: hsl(0 0% 5%);
--background-alt: hsl(0 0% 8%);
--foreground: hsl(0 0% 95%);
--foreground-alt: hsl(0 0% 70%);
--muted: hsl(240 4% 16%);
--muted-foreground: hsla(0 0% 100% / 0.4);
--border: hsl(0 0% 96%);
--border-input: hsla(0 0% 96% / 0.17);
--border-input-hover: hsla(0 0% 96% / 0.4);
--border-card: hsla(0 0% 96% / 0.1);
--dark: hsl(0 0% 96%);
--dark-40: hsl(0 0% 96% / 0.4);
--dark-10: hsl(0 0% 96% / 0.1);
--dark-04: hsl(0 0% 96% / 0.04);
--accent: hsl(204 90% 90%);
--accent-foreground: hsl(204 94% 94%);
--destructive: hsl(350 89% 60%);
--line: hsl(0 0% 9.02%);
--tertiary: hsl(61.3 100% 82.2%);
/* white */
--contrast: hsl(0 0% 100%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.3);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.5) inset;
--shadow-popover: 0px 7px 12px 3px hsla(0deg 0% 0% / 30%);
--shadow-kbd: 0px 2px 0px 0px rgba(255, 255, 255, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.2);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.4);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(244, 244, 245, 0.1);
}
@theme inline {
--color-background: var(--background);
--color-background-alt: var(--background-alt);
--color-foreground: var(--foreground);
--color-foreground-alt: var(--foreground-alt);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-border: var(--border-card);
--color-border-input: var(--border-input);
--color-border-input-hover: var(--border-input-hover);
--color-border-card: var(--border-card);
--color-dark: var(--dark);
--color-dark-10: var(--dark-10);
--color-dark-40: var(--dark-40);
--color-dark-04: var(--dark-04);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-tertiary: var(--tertiary);
--color-line: var(--line);
--color-contrast: var(--contrast);
--shadow-mini: var(--shadow-mini);
--shadow-mini-inset: var(--shadow-mini-inset);
--shadow-popover: var(--shadow-popover);
--shadow-kbd: var(--shadow-kbd);
--shadow-btn: var(--shadow-btn);
--shadow-card: var(--shadow-card);
--shadow-date-field-focus: var(--shadow-date-field-focus);
--text-xxs: 10px;
--radius-card: 16px;
--radius-card-lg: 20px;
--radius-card-sm: 10px;
--radius-input: 9px;
--radius-button: 5px;
--radius-5px: 5px;
--radius-9px: 9px;
--radius-10px: 10px;
--radius-15px: 15px;
--spacing-input: 3rem;
--spacing-input-sm: 2.5rem;
--breakpoint-desktop: 1440px;
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-caret-blink: caret-blink 1s ease-out infinite;
--animate-scale-in: scale-in 0.2s ease;
--animate-scale-out: scale-out 0.15s ease;
--animate-fade-in: fade-in 0.2s ease;
--animate-fade-out: fade-out 0.15s ease;
--animate-enter-from-left: enter-from-left 0.2s ease;
--animate-enter-from-right: enter-from-right 0.2s ease;
--animate-exit-to-left: exit-to-left 0.2s ease;
--animate-exit-to-right: exit-to-right 0.2s ease;
--font-sans: "Inter", "sans-serif";
--font-mono: "Source Code Pro", "monospace";
--font-alt: "Courier", "sans-serif";
--font-display: "Cal Sans", "sans-serif";
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--bits-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--bits-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes caret-blink {
0%,
70%,
100% {
opacity: 1;
}
20%,
50% {
opacity: 0;
}
}
@keyframes enter-from-right {
from {
opacity: 0;
transform: translateX(200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes enter-from-left {
from {
opacity: 0;
transform: translateX(-200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes exit-to-right {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(200px);
}
}
@keyframes exit-to-left {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-200px);
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: rotateX(-10deg) scale(0.9);
}
to {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
}
@keyframes scale-out {
from {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
to {
opacity: 0;
transform: rotateX(-10deg) scale(0.95);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-border-card, currentColor);
}
* {
@apply border-border;
}
html {
-webkit-text-size-adjust: 100%;
font-variation-settings: normal;
scrollbar-color: var(--bg-muted);
}
body {
@apply bg-background text-foreground;
font-feature-settings:
"rlig" 1,
"calt" 1;
}
::selection {
background: #fdffa4;
color: black;
}
}
@layer components {
*:not(body):not(.focus-override) {
outline: none !important;
&:focus-visible {
@apply focus-visible:ring-foreground focus-visible:ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1;
}
}
.link {
@apply hover:text-foreground/80 focus-visible:ring-foreground focus-visible:ring-offset-background rounded-xs focus-visible:outline-hidden inline-flex items-center gap-1 font-medium underline underline-offset-4 focus-visible:ring-2 focus-visible:ring-offset-2;
}
}
Custom Anchor
By default, the DropdownMenu.Content
is anchored to the DropdownMenu.Trigger
component, which determines where the content is positioned.
If you wish to instead anchor the content to a different element, you can pass either a selector string
or an HTMLElement
to the customAnchor
prop of the DropdownMenu.Content
component.
<script lang="ts">
import { DropdownMenu } from "bits-ui";
let customAnchor = $state<HTMLElement>(null!);
</script>
<div bind:this={customAnchor}></div>
<DropdownMenu.Root>
<DropdownMenu.Trigger />
<DropdownMenu.Content {customAnchor}>
<!-- ... -->
</DropdownMenu.Content>
</DropdownMenu.Root>
API Reference
The root component which manages & scopes the state of the dropdown menu.
Property | Details |
---|---|
open | |
onOpenChange | |
onOpenChangeComplete | |
dir | |
children |
Data Attribute | Details |
---|
The button element which toggles the dropdown menu.
Property | Details |
---|---|
disabled | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-state | |
data-menu-trigger |
A component that portals the content of the dropdown menu to the body or a custom target (if provided).
Property | Details |
---|---|
to | |
disabled | |
children |
Data Attribute | Details |
---|
The content displayed when the dropdown menu is open.
Property | Details |
---|---|
side | |
sideOffset | |
align | |
alignOffset | |
arrowPadding | |
avoidCollisions | |
collisionBoundary | |
collisionPadding | |
sticky | |
hideWhenDetached | |
updatePositionStrategy | |
strategy | |
preventScroll | |
customAnchor | |
onEscapeKeydown | |
escapeKeydownBehavior | |
onInteractOutside | |
onFocusOutside | |
interactOutsideBehavior | |
onOpenAutoFocus | |
onCloseAutoFocus | |
trapFocus | |
forceMount | |
preventOverflowTextSelection | |
dir | |
loop | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-state | |
data-menu-content |
CSS Variable | Details |
---|---|
--bits-dropdown-menu-content-transform-origin | |
--bits-dropdown-menu-content-available-width | |
--bits-dropdown-menu-content-available-height | |
--bits-dropdown-menu-anchor-width | |
--bits-dropdown-menu-anchor-height |
The content displayed when the dropdown menu is open. (Static/No Floating UI)
Property | Details |
---|---|
onEscapeKeydown | |
escapeKeydownBehavior | |
onInteractOutside | |
onFocusOutside | |
interactOutsideBehavior | |
onOpenAutoFocus | |
onCloseAutoFocus | |
trapFocus | |
preventScroll | |
forceMount | |
preventOverflowTextSelection | |
dir | |
loop | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-state | |
data-menu-content |
A menu item within the dropdown menu.
Property | Details |
---|---|
disabled | |
textValue | |
onSelect | |
closeOnSelect | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-orientation | |
data-highlighted | |
data-disabled | |
data-menu-item |
A group of checkbox menu items, where multiple can be checked at a time.
Property | Details |
---|---|
value | |
onValueChange | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-menu-checkbox-group |
A menu item that can be controlled and toggled like a checkbox.
Property | Details |
---|---|
disabled | |
checked | |
onCheckedChange | |
indeterminate | |
onIndeterminateChange | |
value | |
textValue | |
onSelect | |
closeOnSelect | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-orientation | |
data-highlighted | |
data-disabled | |
data-state |
A group of radio menu items, where only one can be checked at a time.
Property | Details |
---|---|
value | |
onValueChange | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-menu-radio-group |
A menu item that can be controlled and toggled like a radio button. It must be a child of a RadioGroup
.
Property | Details |
---|---|
value | |
disabled | |
textValue | |
onSelect | |
closeOnSelect | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-orientation | |
data-highlighted | |
data-disabled | |
data-state | |
data-value | |
data-menu-radio-item |
A horizontal line to visually separate menu items.
Property | Details |
---|---|
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-orientation | |
data-menu-separator |
An optional arrow which points to the dropdown menu's anchor/trigger point.
Property | Details |
---|---|
width | |
height | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-state | |
data-menu-arrow |
A group of menu items. It should be passed an aria-label
or have a child DropdownMenu.GroupHeading
component to provide a label for a group of menu items.
Property | Details |
---|---|
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-menu-group |
A heading for a group which will be skipped when navigating with the keyboard. It is used to provide a description for a group of menu items and must be a child of either a DropdownMenu.Group
or DropdownMenu.RadioGroup
component.
Property | Details |
---|---|
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-menu-group-heading |
A submenu belonging to the parent dropdown menu. Responsible for managing the state of the submenu.
Property | Details |
---|---|
open | |
onOpenChange | |
onOpenChangeComplete | |
children |
Data Attribute | Details |
---|
A menu item which when pressed or hovered, opens the submenu it is a child of.
Property | Details |
---|---|
disabled | |
textValue | |
onSelect | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-orientation | |
data-highlighted | |
data-disabled | |
data-state | |
data-menu-sub-trigger |
The submenu content displayed when the parent submenu is open.
Property | Details |
---|---|
side | |
sideOffset | |
align | |
alignOffset | |
arrowPadding | |
avoidCollisions | |
collisionBoundary | |
collisionPadding | |
sticky | |
hideWhenDetached | |
updatePositionStrategy | |
strategy | |
preventScroll | |
customAnchor | |
onEscapeKeydown | |
escapeKeydownBehavior | |
onInteractOutside | |
onFocusOutside | |
interactOutsideBehavior | |
onOpenAutoFocus | |
onCloseAutoFocus | |
trapFocus | |
forceMount | |
preventOverflowTextSelection | |
dir | |
loop | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-state | |
data-menu-sub-content |
The submenu content displayed when the parent submenu menu is open. (Static/No Floating UI)
Property | Details |
---|---|
onEscapeKeydown | |
escapeKeydownBehavior | |
onInteractOutside | |
onFocusOutside | |
interactOutsideBehavior | |
onOpenAutoFocus | |
onCloseAutoFocus | |
trapFocus | |
forceMount | |
preventOverflowTextSelection | |
dir | |
loop | |
ref | |
children | |
child |
Data Attribute | Details |
---|---|
data-state | |
data-menu-sub-content |