Component

Profile menu

<DomProfileMenu>

A signed-in account trigger with avatar, email, and app profile actions.

Playground

Try every prop live

Profile menu playground

Edit the identity props and menu rows, then open the menu to test keyboard navigation and item selection.

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

const data = reactive({
	  "name": "Mina Patel",
	  "email": "mina@northstar.app",
	  "avatar": "",
	  "initials": "MP",
	  "triggerLabel": "",
	  "showName": false,
	  "showEmail": true,
	  "avatarSize": "sm",
	  "themeMode": null,
	  "themeControl": true,
	  "items": null,
	  "align": "right",
	  "placement": "bottom",
	  "width": "min-w-[18rem]",
	  "collisionPadding": 8,
	  "floatingMode": "viewport",
	  "lockScroll": false
	});
</script>

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

Demo

Signed-in app trigger

The account trigger renders avatar, email, and chevron. The default menu is composed from MenuItem, MenuLabel, MenuSeparator, and DomThemeSwitcher as a profile row accessory.

Selected: none/ Theme: stored preference

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

const selected = ref('none');
const themeMode = ref('stored preference');
</script>

<template>
	<div class="grid gap-3">
		<DomProfileMenu
			name="Mina Patel"
			email="mina@northstar.app"
			initials="MP"
			@select="selected = $event.value"
			@theme="themeMode = $event.mode"
		/>

		<p class="text-xs text-muted-fg">
			Selected: <code class="text-canvas-fg">{{ selected }}</code>
			<span class="px-1 text-border">/</span>
			Theme: <code class="text-canvas-fg">{{ themeMode }}</code>
		</p>
	</div>
</template>

Demo

Application header

Use show-name when the app chrome has room for a two-line identity trigger.

Northstar Studio

Production workspace

Account action

The last selected profile menu item is none. Theme mode is stored preference.

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

const action = ref('none');
const themeMode = ref('stored preference');
</script>

<template>
	<div class="w-full max-w-3xl overflow-hidden rounded-2xl border border-border bg-canvas shadow-sm">
		<header class="flex items-center justify-between gap-4 border-b border-border px-4 py-3">
			<div class="min-w-0">
				<p class="truncate text-sm font-semibold">Northstar Studio</p>
				<p class="truncate text-xs text-muted-fg">Production workspace</p>
			</div>

			<div class="flex items-center gap-2">
				<DomButton size="sm" variant="secondary">Invite</DomButton>
				<DomProfileMenu
					name="Avery Stone"
					email="avery@northstar.app"
					initials="AS"
					show-name
					width="min-w-[18rem]"
					@select="action = $event.value"
					@theme="themeMode = $event.mode"
				/>
			</div>
		</header>

		<div class="grid gap-2 px-4 py-5 text-sm">
			<p class="font-medium">Account action</p>
			<p class="text-muted-fg">
				The last selected profile menu item is <code class="text-canvas-fg">{{ action }}</code>.
				Theme mode is <code class="text-canvas-fg">{{ themeMode }}</code>.
			</p>
		</div>
	</div>
</template>

Demo

Items data

Pass items for the same profile/onboarding/theme/logout shape: label rows, separators, hrefs, submenus, tone, and accessorySlot.



Selected: none/ Theme: stored preference

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

const selected = ref('none');
const themeMode = ref('stored preference');

const items = [
	{ type: 'label', value: 'signed-in-as', label: 'Logged in as', description: 'steven.a.obrien@gmail.com' },
	{ separator: true },
	{ value: 'profile', label: 'Profile', description: 'Account details and preferences', icon: 'profile', href: '#profile', accessorySlot: 'theme-switcher' },
	{
		value: 'onboarding',
		label: 'Onboarding',
		description: 'Resume setup checklist',
		icon: 'onboarding',
		children: [
			{ value: 'add-website', label: 'Add website', type: 'task', href: '#add-website', completed: true },
			{ value: 'audience-competitors', label: 'Audience and competitors', type: 'task', href: '#audience-competitors', completed: false },
			{ value: 'integrate-blog', label: 'Integrate blog', type: 'task', href: '#integrate-blog', completed: false },
			{ value: 'connect-search-console', label: 'Connect Google search console', type: 'task', href: '#connect-search-console', completed: false },
		],
	},
	{ separator: true },
	{ value: 'logout', label: 'Log out', description: 'End the current session', icon: 'logout', tone: 'danger' },
];
</script>

<template>
	<div class="grid gap-3">
		<DomProfileMenu
			name="Steven O'Brien"
			email="steven.a.obrien@gmail.com"
			initials="SO"
			:items="items"
			@select="selected = $event.value"
			@theme="themeMode = $event.mode"
		/>

		<p class="text-xs text-muted-fg">
			Selected: <code class="text-canvas-fg">{{ selected }}</code>
			<span class="px-1 text-border">/</span>
			Theme: <code class="text-canvas-fg">{{ themeMode }}</code>
		</p>
	</div>
</template>

Demo

Component rows

Use the item slot to resolve item.component or item.componentName, then render a custom Vue component inside the keyboard-managed menu row.


Selected: none/ Theme: stored preference

<script setup>
import { ref } from 'vue';
import { DomProfileMenu } from '@getdom/studio/vue';
import ProfileMenuDefaultRow from './ProfileMenuDefaultRow.vue';
import ProfileMenuUsageRow from './ProfileMenuUsageRow.vue';

const selected = ref('none');
const themeMode = ref('stored preference');

const rowComponents = {
	default: ProfileMenuDefaultRow,
	usage: ProfileMenuUsageRow,
};

const items = [
	{ value: 'profile', label: 'Profile', description: 'Name, email, and avatar', icon: 'profile', href: '#profile', accessorySlot: 'theme-switcher' },
	{ value: 'usage', label: 'Plan usage', description: '8.2k of 10k events this month', percent: 82, componentName: 'usage' },
	{ value: 'billing', label: 'Billing', description: 'Invoices, seats, and payment method', icon: 'billing', href: '#billing' },
	{
		value: 'onboarding',
		label: 'Onboarding',
		description: 'Resume setup checklist',
		icon: 'onboarding',
		children: [
			{ value: 'add-website', label: 'Add website', type: 'task', href: '#add-website', completed: true },
			{ value: 'audience-competitors', label: 'Audience and competitors', type: 'task', href: '#audience-competitors', completed: false },
		],
	},
	{ separator: true },
	{ value: 'logout', label: 'Log out', description: 'End the current session', icon: 'logout', tone: 'danger' },
];

function componentFor(item) {
	return item.component || rowComponents[item.componentName] || rowComponents.default;
}
</script>

<template>
	<div class="grid gap-3">
		<DomProfileMenu
			name="Mina Patel"
			email="mina@northstar.app"
			initials="MP"
			:items="items"
			@select="selected = $event.value"
			@theme="themeMode = $event.mode"
		>
			<template #item="{ item, label, value }">
				<component :is="componentFor(item)" :item="item" :label="label" :value="value" />
			</template>
		</DomProfileMenu>

		<p class="text-xs text-muted-fg">
			Selected: <code class="text-canvas-fg">{{ selected }}</code>
			<span class="px-1 text-border">/</span>
			Theme: <code class="text-canvas-fg">{{ themeMode }}</code>
		</p>
	</div>
</template>

Reference

Props

Control props

NameTypeTSDefaultDescription
namestringstring''User display name used for avatar fallback and optional trigger text.
emailstringstring''Signed-in email shown in the trigger by default.
avatarstringstring''Avatar image URL.
initialsstringstring''Explicit initials for the avatar fallback.
triggerLabelstringstring''Fallback trigger text when no email or name is available.
showNamebooleanbooleanfalseShow the name in the trigger. When email is also shown, it appears as secondary text.
showEmailbooleanbooleantrueShow the email address in the trigger.
avatarSize'xs' | 'sm' | 'md'string'sm'Avatar size inside the trigger.
themeMode'dark' | 'light' | 'system'stringOptional controlled theme mode for the embedded theme switcher. Omit it to use the stored DOM Studio preference.
themeControlbooleanbooleantrueRender a segmented dark / light / system control as a profile row accessory.
items
[
	{
		label: "Profile",
		value: "profile",
		href: "/settings/profile",
		description: "Account details and preferences",
		icon: "profile",
		shortcut: "⌘K",
		accessorySlot: "theme-switcher",
		componentName: "usage",
		type: "label",
		tone: "danger",
	}
]
arrayArray<ItemsItem
type ItemsItem = {
	label?: string; // Label
	value?: string; // Value
	href?: string; // Href
	description?: string; // Description
	icon?: string; // Icon
	shortcut?: string; // Shortcut
	accessorySlot?: string; // Accessory slot
	componentName?: string; // Component
	type?: string; // Type
	tone?: string; // Tone
};
>
Optional data-driven menu rows. Use separator: true, type: "label" for static rows, children for submenus, shortcut for right-side commands, accessorySlot for custom right-side content, and componentName as a marker for the #item slot.
align'left' | 'right'string'right'Where the menu opens relative to the trigger.
placement'bottom' | 'top' | 'right' | 'left'string'bottom'Preferred side before collision handling.
widthstringstring'min-w-[18rem]'Tailwind width utility for the dropdown panel.
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.

Auto-generated from Profile menu.props and inline _edit hints.

Slots

NameScopeDescription
#trigger{ name, email, initials }Replace the avatar and identity text inside the dropdown trigger.
#item{ item, label, value }Replace the markup for selectable data-driven menu items. Useful for resolving item.component or item.componentName to a custom row component.
#item-label{ item, label, value }Replace the markup for non-selectable data-driven label/static rows.

Events

NamePayloadDescription
@select({ value, item })Fired when an action item is chosen.
@change({ value, checked, item })Fired when a checkbox or radio menu item changes.
@update:themeMode(mode
mode: "dark" | "light" | "system";
: "dark" | "light" | "system")
Fired when the embedded theme switcher changes.
@profile({ value, item })Fired when the profile action is chosen.
@onboarding({ value, item })Fired when the onboarding action is chosen.
@theme({ value, item, mode })Fired when the theme mode changes, or when a plain theme item is chosen.
@logout({ value, item })Fired when the logout action is chosen.

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