Component

Dropdown

<DomDropdown>

A menu that opens from a button — fully keyboard accessible, with focus return, optional scroll lock, and outside-click handling.

Playground

Try every prop live

Dropdown playground

Edit props in the inspector — open the menu to preview item labels and alignment.

Playground.vuevue
vue
<script setup>
import { reactive } from 'vue';
import { DomDropdown } from '@getdom/studio/vue';

const data = reactive({
	  "items": [
	    {
	      "label": "First option",
	      "value": "a"
	    },
	    {
	      "label": "Second option",
	      "value": "b"
	    },
	    {
	      "label": "Third option",
	      "value": "c"
	    }
	  ],
	  "label": "Choose…",
	  "align": "left",
	  "placement": "bottom",
	  "collisionPadding": 8,
	  "floatingMode": "viewport",
	  "lockScroll": false,
	  "width": "min-w-[12rem]",
	  "panelType": "menu",
	  "autoFocusPanel": true,
	  "focusValue": null,
	  "disabled": false,
	  "triggerClass": null,
	  "showIndicator": true
	});
</script>

<template>
	<DomDropdown
		v-bind="data"
	/>
</template>

Demo

Basic

Click the trigger to open the menu. Selected value is emitted via @select.

Selected:

Basic.vuevue
vue
<script setup>
import { ref } from 'vue';
import { DomDropdown } from '@getdom/studio/vue';

const items = [
	{ label: 'View profile', value: 'profile' },
	{ label: 'Billing & plans', value: 'billing' },
	{ label: 'Team settings', value: 'team' },
	{ label: 'Sign out', value: 'signout' },
];
const selected = ref('');
</script>

<template>
	<div class="flex flex-col items-center gap-3">
		<DomDropdown :items="items" label="Account ▾" @select="(v) => selected = v" />
		<p class="text-xs text-muted-fg">
			Selected: <code class="text-canvas-fg">{{ selected || '—' }}</code>
		</p>
	</div>
</template>

Demo

Custom item markup

The #trigger and #item slots accept any markup — avatars, descriptions, badges.

Use DomSelect when this chosen person is a form value.

Rich.vuevue
vue
<script setup>
import { computed, ref } from 'vue';
import { RouterLink } from 'vue-router';
import { DomAvatar, DomDropdown } from '@getdom/studio/vue';

const items = [
	{ value: 'sara', name: 'Sara Patel', role: 'Design', initials: 'SP', avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
	{ value: 'tomas', name: 'Tomás Ruiz', role: 'Engineering', initials: 'TR', avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
	{ value: 'priya', name: 'Priya Nair', role: 'Product', initials: 'PN', avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
	{ value: 'leo', name: 'Leo Almeida', role: 'Operations', initials: 'LA', avatar: 'https://images.unsplash.com/photo-1519345182560-3f2917c472ef?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
];
const assigned = ref('sara');
const current = computed(() => items.find((i) => i.value === assigned.value));
</script>

<template>
	<div class="grid w-full max-w-sm gap-3">
		<DomDropdown :items="items" :focus-value="assigned" width="min-w-[16rem]" @select="(v) => assigned = v">
			<template #trigger>
				<span class="flex items-center gap-2">
					<DomAvatar :src="current?.avatar" :name="current?.name" :initials="current?.initials" size="xs" />
					<span>{{ current?.name }}</span>
				</span>
			</template>

			<template #item="{ item }">
				<div class="flex items-center gap-3">
					<DomAvatar :src="item.avatar" :name="item.name" :initials="item.initials" size="sm" />
					<div class="min-w-0">
						<p class="truncate text-sm font-medium text-canvas-fg">{{ item.name }}</p>
						<p class="truncate text-xs text-muted-fg">{{ item.role }}</p>
					</div>
				</div>
			</template>
		</DomDropdown>
		<p class="text-xs leading-5 text-muted-fg">
			Use <RouterLink to="/forms/select" class="font-medium text-canvas-fg underline-offset-4 hover:underline">DomSelect</RouterLink> when this chosen person is a form value.
		</p>
	</div>
</template>

Demo

Workspace switcher

Use rich dropdown rows for workspace, account, team, and organization switchers.

Use DomSelect when workspace selection needs form semantics.

vue
<script setup>
import { computed, ref } from 'vue';
import { RouterLink } from 'vue-router';
import { DomAvatar, DomAvatarStack, DomBadge, DomDropdown, DomStatusPill } from '@getdom/studio/vue';

const workspaces = [
	{
		value: 'northstar',
		label: 'Northstar Studio',
		description: 'Design systems and product audit workspace',
		initials: 'NS',
		src: 'https://images.unsplash.com/photo-1527980965255-d3b416303d12?auto=format&fit=crop&crop=faces&w=96&h=96&q=80',
		tone: 'success',
		plan: 'Scale',
		members: [
			{ name: 'Mina Patel', initials: 'MP', src: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
			{ name: 'Ari Chen', initials: 'AC', src: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
			{ name: 'Noah Kim', initials: 'NK', src: 'https://images.unsplash.com/photo-1519345182560-3f2917c472ef?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
		],
	},
	{
		value: 'vector',
		label: 'Vector Labs',
		description: 'AI workflow experiments and eval reports',
		initials: 'VL',
		src: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=80',
		tone: 'info',
		plan: 'Pro',
		members: [
			{ name: 'Leah Stone', initials: 'LS', src: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
			{ name: 'Theo Grant', initials: 'TG', src: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
		],
	},
	{
		value: 'harbor',
		label: 'Harbor Ops',
		description: 'Support, billing, and incident operations',
		initials: 'HO',
		src: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&crop=faces&w=96&h=96&q=80',
		tone: 'warning',
		plan: 'Business',
		members: [
			{ name: 'Sam Green', initials: 'SG', src: 'https://images.unsplash.com/photo-1527980965255-d3b416303d12?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
			{ name: 'Ida Bell', initials: 'IB', src: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
			{ name: 'Kai Rivers', initials: 'KR', src: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
			{ name: 'June Hart', initials: 'JH', src: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
		],
	},
];

const selected = ref('northstar');
const current = computed(() => workspaces.find((workspace) => workspace.value === selected.value));
</script>

<template>
	<div class="grid w-full max-w-sm gap-3">
		<DomDropdown :items="workspaces" :focus-value="selected" width="min-w-[22rem]" @select="(value) => selected = value">
			<template #trigger>
				<span class="flex min-w-0 items-center gap-2">
					<DomAvatar :src="current?.src" :name="current?.label" :initials="current?.initials" size="xs" />
					<span class="truncate">{{ current?.label }}</span>
					<DomStatusPill :tone="current?.tone" size="sm">{{ current?.plan }}</DomStatusPill>
				</span>
			</template>

			<template #item="{ item }">
				<span class="flex items-start gap-3">
					<DomAvatar :src="item.src" :name="item.label" :initials="item.initials" size="sm" />
					<span class="min-w-0 flex-1">
						<span class="flex items-center gap-2">
							<span class="truncate font-medium">{{ item.label }}</span>
							<DomStatusPill :tone="item.tone" size="sm">{{ item.plan }}</DomStatusPill>
						</span>
						<span class="mt-1 block text-xs leading-5 text-muted-fg">{{ item.description }}</span>
						<span class="mt-2 flex items-center justify-between gap-3">
							<DomAvatarStack :items="item.members" size="xs" :max="3" :label="`${item.label} members`" />
							<DomBadge v-if="item.value === selected" tone="success" size="sm">Current</DomBadge>
						</span>
					</span>
				</span>
			</template>
		</DomDropdown>
		<p class="text-xs leading-5 text-muted-fg">
			Use <RouterLink to="/forms/select" class="font-medium text-canvas-fg underline-offset-4 hover:underline">DomSelect</RouterLink> when workspace selection needs form semantics.
		</p>
	</div>
</template>

Demo

Status menu

Dropdown menus are best for actions. Use rich rows, shortcuts, selected state, and severity tone when the menu changes workflow state.


Dropdowns are for actions. Use DomSelect when the selection is a form value.

vue
<script setup>
import { computed, ref } from 'vue';
import { RouterLink } from 'vue-router';
import { DomBadge, DomDropdown, DomStatusPill } from '@getdom/studio/vue';

const statuses = [
	{ value: 'draft', label: 'Draft', tone: 'neutral', description: 'Visible to editors only.', shortcut: 'D' },
	{ value: 'review', label: 'In review', tone: 'warning', description: 'Ready for stakeholder approval.', shortcut: 'R' },
	{ value: 'live', label: 'Live', tone: 'success', description: 'Published to customers.', shortcut: 'L' },
	{ separator: true },
	{ value: 'archived', label: 'Archived', tone: 'danger', description: 'Hidden from active workflows.', shortcut: 'A' },
];
const status = ref('review');
const current = computed(() => statuses.find((item) => item.value === status.value));
</script>

<template>
	<div class="grid w-full max-w-sm gap-3">
		<DomDropdown :items="statuses" :focus-value="status" label="Change status" width="min-w-[19rem]" @select="(value) => status = value">
			<template #trigger>
				<span class="flex items-center gap-2">
					<DomStatusPill :tone="current?.tone">{{ current?.label }}</DomStatusPill>
				</span>
			</template>

			<template #item="{ item }">
				<span class="flex items-start justify-between gap-4" :class="item.tone === 'danger' && 'text-destructive'">
					<span class="min-w-0">
						<span class="flex items-center gap-2">
							<DomStatusPill :tone="item.tone" size="sm">{{ item.label }}</DomStatusPill>
							<DomBadge v-if="item.value === status" tone="success" size="sm">Selected</DomBadge>
						</span>
						<span class="mt-1 block text-xs leading-5 text-muted-fg">{{ item.description }}</span>
					</span>
					<kbd class="rounded-md border border-border bg-canvas px-1.5 py-0.5 text-[11px] font-medium text-muted-fg">
						{{ item.shortcut }}
					</kbd>
				</span>
			</template>
		</DomDropdown>
		<p class="text-xs text-muted-fg">
			Dropdowns are for actions. Use <RouterLink to="/forms/select" class="font-medium text-canvas-fg underline-offset-4 hover:underline">DomSelect</RouterLink> when the selection is a form value.
		</p>
	</div>
</template>

Demo

Actions menu — icon + label

Items with separator: true draw a divider. tone: 'danger' colours destructive rows red.


vue
<script setup>
import { ref } from 'vue';
import { DomDropdown } from '@getdom/studio/vue';

const icons = {
	edit: '<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />',
	duplicate: '<rect x="9" y="9" width="13" height="13" rx="2" ry="2" /><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />',
	share: '<circle cx="18" cy="5" r="3" /><circle cx="6" cy="12" r="3" /><circle cx="18" cy="19" r="3" /><line x1="8.59" y1="13.51" x2="15.42" y2="17.49" /><line x1="15.41" y1="6.51" x2="8.59" y2="10.49" />',
	archive: '<polyline points="21 8 21 21 3 21 3 8" /><rect x="1" y="3" width="22" height="5" /><line x1="10" y1="12" x2="14" y2="12" />',
	trash: '<polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><line x1="10" y1="11" x2="10" y2="17" /><line x1="14" y1="11" x2="14" y2="17" />',
};
const actions = [
	{ label: 'Edit', value: 'edit', icon: 'edit' },
	{ label: 'Duplicate', value: 'duplicate', icon: 'duplicate' },
	{ label: 'Share', value: 'share', icon: 'share' },
	{ label: 'Archive', value: 'archive', icon: 'archive' },
	{ separator: true },
	{ label: 'Delete', value: 'delete', icon: 'trash', tone: 'danger' },
];
const lastAction = ref('');
</script>

<template>
	<div class="flex flex-col items-center gap-3">
		<DomDropdown
			:items="actions"
			label="Actions"
			align="right"
			width="min-w-[14rem]"
			@select="(v) => lastAction = v"
		>
			<template #item="{ item }">
				<span class="flex items-center gap-2.5" :class="item.tone === 'danger' && 'text-destructive'">
					<svg
						viewBox="0 0 24 24"
						class="size-4 opacity-80"
						fill="none"
						stroke="currentColor"
						stroke-width="1.6"
						stroke-linecap="round"
						stroke-linejoin="round"
						v-html="icons[item.icon]"
					/>
					<span>{{ item.label }}</span>
				</span>
			</template>
		</DomDropdown>
		<p v-if="lastAction" class="text-xs text-muted-fg">
			Last action: <code class="text-canvas-fg">{{ lastAction }}</code>
		</p>
	</div>
</template>

Demo

Composed menu

Use the default slot for a composed DomMenu panel. MenuItem keeps selection and keyboard behaviour while #accessory and MenuLabel handle rich row content.

Selected: none

vue
<script setup>
import { ref } from 'vue';
import { DomAvatar, DomDropdown, DomMenu, MenuItem, MenuLabel, MenuSeparator, DomThemeSwitcher } from '@getdom/studio/vue';

const selected = ref('none');
const onboardingTasks = [
	{ value: 'website', label: 'Add website', href: '#add-website', completed: true },
	{ value: 'audience', label: 'Audience and competitors', href: '#audience-competitors', completed: false },
	{ value: 'blog', label: 'Integrate blog', href: '#integrate-blog', completed: false },
];

const icons = {
	profile: ['M20 21a8 8 0 0 0-16 0', 'M12 13a4 4 0 1 0 0-8 4 4 0 0 0 0 8z'],
	onboarding: ['M9 11l3 3L22 4', 'M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11'],
	logout: ['M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4', 'M16 17l5-5-5-5', 'M21 12H9'],
};

function icon(name) {
	return icons[name] || [];
}
</script>

<template>
	<div class="grid gap-3">
		<DomDropdown align="right" width="min-w-[18rem]" :auto-focus-panel="false" panel-type="dialog" @select="selected = $event">
			<template #trigger>
				<span class="flex min-w-0 items-center gap-2">
					<DomAvatar initials="SO" name="Steven O'Brien" size="sm" />
					<span class="truncate text-sm">steven.a.obrien@gmail.com</span>
				</span>
			</template>

			<DomMenu>
				<MenuItem value="profile" href="#profile">
					<span class="flex min-w-0 items-center gap-3">
						<span class="grid size-8 shrink-0 place-items-center rounded-full bg-secondary text-muted-fg">
							<svg v-if="icon('profile').length" viewBox="0 0 24 24" class="size-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
								<path v-for="path in icon('profile')" :key="path" :d="path" />
							</svg>
						</span>
						<span class="min-w-0">
							<span class="block truncate font-medium">Profile</span>
							<span class="block truncate text-xs text-muted-fg">Account details and preferences</span>
						</span>
					</span>
					<template #accessory>
						<DomThemeSwitcher :show-label="false" size="icon-sm" />
					</template>
				</MenuItem>

				<MenuItem value="onboarding">
					<span class="flex min-w-0 items-center gap-3">
						<span class="grid size-8 shrink-0 place-items-center rounded-full bg-secondary text-muted-fg">
							<svg v-if="icon('onboarding').length" viewBox="0 0 24 24" class="size-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
								<path v-for="path in icon('onboarding')" :key="path" :d="path" />
							</svg>
						</span>
						<span class="min-w-0">
							<span class="block truncate font-medium">Onboarding</span>
							<span class="block truncate text-xs text-muted-fg">Resume setup checklist</span>
						</span>
					</span>
					<template #submenu>
						<MenuItem v-for="task in onboardingTasks" :key="task.value" :value="task.value" :href="task.href">
							<span class="flex min-w-0 items-center gap-3">
								<span class="grid size-4 shrink-0 place-items-center rounded-full border" :class="task.completed ? 'border-primary bg-primary text-primary-fg' : 'border-border bg-canvas text-muted-fg'" aria-hidden="true">
									<svg v-if="task.completed" viewBox="0 0 16 16" class="size-3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.2l2.7 2.7 6.3-6.6" /></svg>
								</span>
								<span class="min-w-0 truncate" :class="task.completed ? 'text-muted-fg line-through decoration-border decoration-2' : ''">{{ task.label }}</span>
							</span>
						</MenuItem>
					</template>
				</MenuItem>

				<MenuSeparator />
				<MenuLabel>
					<span class="block text-[11px] font-medium uppercase text-muted-fg">Logged in as</span>
					<span class="mt-0.5 block truncate text-sm font-medium text-canvas-fg">steven.a.obrien@gmail.com</span>
				</MenuLabel>
				<MenuSeparator />

				<MenuItem value="logout" tone="danger">
					<span class="flex min-w-0 items-center gap-3">
						<span class="grid size-8 shrink-0 place-items-center rounded-full bg-destructive/10 text-destructive">
							<svg v-if="icon('logout').length" viewBox="0 0 24 24" class="size-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
								<path v-for="path in icon('logout')" :key="path" :d="path" />
							</svg>
						</span>
						<span class="min-w-0">
							<span class="block truncate font-medium">Log out</span>
							<span class="block truncate text-xs text-destructive/80">End the current session</span>
						</span>
					</span>
				</MenuItem>
			</DomMenu>
		</DomDropdown>

		<p class="text-xs text-muted-fg">Selected: <code class="text-canvas-fg">{{ selected }}</code></p>
	</div>
</template>

Demo

Tree view panel

The #panel slot can host composite controls, so the tree keeps its own focus and arrow-key behaviour inside a scroll-locking dropdown.

Move to

Workspace
Overview
Design system
Product
Roadmap
Feedback

Destination: Design system

vue
<script setup>
import { computed, ref } from 'vue';
import { DomDropdown, DomTreeView } from '@getdom/studio/vue';

const folderIcon = 'M4 6h6l2 2h8v10H4V6Z';
const screenIcon = 'M5 5h14v10H5V5Zm4 14h6M10 15l-.5 4M14 15l.5 4';
const settingsIcon = 'M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0-5v3M12 18v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M3 12h3M18 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1';
const fileIcon = 'M6 4h8l4 4v12H6V4Zm8 0v5h5';

const dropdown = ref(null);
const selected = ref('workspace-design-system');
const sections = ref([
	{
		id: 'workspace',
		label: 'Workspace',
		icon: folderIcon,
		open: true,
		children: [
			{ id: 'workspace-overview', label: 'Overview', icon: screenIcon },
			{ id: 'workspace-design-system', label: 'Design system', icon: settingsIcon },
			{ id: 'workspace-release-notes', label: 'Release notes', icon: fileIcon, children: [
				{ id: 'workspace-release-notes-1.0.0', label: '1.0.0', icon: fileIcon },
				{ id: 'workspace-release-notes-1.0.1', label: '1.0.1', icon: fileIcon },
				{ id: 'workspace-release-notes-1.0.2', label: '1.0.2', icon: fileIcon, children: [
					{ id: 'workspace-release-notes-1.0.2-1.0.0', label: '1.0.0', icon: fileIcon },
					{ id: 'workspace-release-notes-1.0.2-1.0.1', label: '1.0.1 best practices', icon: fileIcon, children: [
						{ id: 'workspace-release-notes-1.0.2-1.0.1-1.0.0', label: '1.0.0 The complete guide to Using AI', icon: fileIcon },
					] },
				] },
			] },
		],
	},
	{
		id: 'product',
		label: 'Product',
		icon: folderIcon,
		open: true,
		children: [
			{ id: 'product-roadmap', label: 'Roadmap', icon: fileIcon },
			{ id: 'product-feedback', label: 'Feedback', icon: screenIcon },
		],
	},
	{
		id: 'operations',
		label: 'Operations',
		icon: folderIcon,
		children: [
			{ id: 'operations-hiring', label: 'Hiring', icon: fileIcon },
			{ id: 'operations-finance', label: 'Finance', icon: fileIcon },
		],
	},
]);

function findSection(nodes, value) {
	for (const node of nodes) {
		if (node.id === value) return node;
		const match = node.children ? findSection(node.children, value) : null;
		if (match) return match;
	}
	return null;
}

const selectedSection = computed(() => findSection(sections.value, selected.value));

function chooseSection({ value }) {
	selected.value = value;
	dropdown.value?.close();
}
</script>

<template>
	<div class="flex flex-col items-center gap-3">
		<DomDropdown
			ref="dropdown"
			label="Move to"
			width="w-80"
			panel-type="tree"
			lock-scroll
		>
			<template #trigger>
				<span class="flex min-w-0 items-center gap-2">
					<svg viewBox="0 0 24 24" class="size-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round">
						<path :d="folderIcon" />
					</svg>
					<span class="max-w-40 truncate">{{ selectedSection?.label }}</span>
				</span>
			</template>

			<template #panel>
				<div class="w-full p-1">
					<div class="px-2 pb-2 pt-1">
						<p class="text-xs font-medium uppercase tracking-wide text-muted-fg">Move to</p>
					</div>
					<DomTreeView
						v-model="selected"
						:items="sections"
						:draggable="false"
						:chrome="false"
						density="comfortable"
						variant="finder"
						label="Project sections"
						@select="chooseSection"
					/>
				</div>
			</template>
		</DomDropdown>
		<p class="text-xs text-muted-fg">
			Destination: <code class="text-canvas-fg">{{ selectedSection?.label }}</code>
		</p>
	</div>
</template>

Reference

Props

Control props

NameTypeTSDefaultDescription
items
ts
[
	{
		label: "Item 1",
		value: "item-1",
	}
]
arrayArray<ItemsItem
type ItemsItem = {
	label?: string; // Label
	value?: string; // Value
};
>
[]Menu items shown when the dropdown opens.
labelstringstring'Options'Trigger button label (overridden by the #trigger slot if used).
align'left' | 'right'string'left'Where the menu opens relative to the trigger.
placement'bottom' | 'top' | 'right' | 'left'string'bottom'Preferred side before collision handling.
collisionPaddingnumbernumber8Viewport padding used when the menu flips or shifts.
floatingMode'viewport' | 'anchor'string'viewport'viewport keeps the menu inside the browser; anchor keeps it attached while scrolling.
lockScrollbooleanbooleanfalseLock browser scrolling while the dropdown is open.
widthstringstring'min-w-[12rem]'Tailwind width utility for the menu (e.g. min-w-[16rem]).
panelType'menu' | 'tree' | 'dialog' | 'listbox' | 'grid'string'menu'Accessible popup type. Use tree/dialog/etc when rendering the #panel slot.
autoFocusPanelbooleanbooleantrueFocus the first focusable element when opening a custom panel.
focusValuestring | numberstringMenu item value to prefer when opening from the keyboard.
disabledbooleanbooleanfalseDisable the trigger and prevent the dropdown from opening.
triggerClassstring | array | objectArray<unknown>Override the trigger button classes when composing dropdowns into toolbars or compact controls.
showIndicatorbooleanbooleantrueShow the chevron indicator in the trigger.

Auto-generated from Dropdown.props and inline _edit hints.

Slots

NameScopeDescription
#(default){ close, open, toggle }Custom panel content, usually a DomMenu composed with MenuItem rows.
#triggerReplaces the inner content of the trigger button.
#item{ item, index }Replaces the rendering of each menu item.
#panel{ close, open, toggle }Replaces menu items with custom dropdown content, such as a tree view or form.

Events

NamePayloadDescription
@select(value
value: string;
: string)
Fired when a menu item is chosen.

Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.

Keyboard

  • Enter / Space / ↓Open menu (when trigger is focused).
  • ↑ / ↓Move active item.
  • Home / EndJump to first / last item.
  • EnterSelect active item.
  • Esc / TabClose menu and return focus.