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.
<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
<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
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
name | string | string | '' | User display name used for avatar fallback and optional trigger text. |
email | string | string | '' | Signed-in email shown in the trigger by default. |
avatar | string | string | '' | Avatar image URL. |
initials | string | string | '' | Explicit initials for the avatar fallback. |
triggerLabel | string | string | '' | Fallback trigger text when no email or name is available. |
showName | boolean | boolean | false | Show the name in the trigger. When email is also shown, it appears as secondary text. |
showEmail | boolean | boolean | true | Show the email address in the trigger. |
avatarSize | 'xs' | 'sm' | 'md' | string | 'sm' | Avatar size inside the trigger. |
themeMode | 'dark' | 'light' | 'system' | string | — | Optional controlled theme mode for the embedded theme switcher. Omit it to use the stored DOM Studio preference. |
themeControl | boolean | boolean | true | Render a segmented dark / light / system control as a profile row accessory. |
items | array | Array< | — | 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. |
width | string | string | 'min-w-[18rem]' | Tailwind width utility for the dropdown panel. |
collisionPadding | number | number | 8 | Viewport 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. |
lockScroll | boolean | boolean | false | Lock browser scrolling while the dropdown is open. |
Auto-generated from Profile menu.props and inline _edit hints.
Slots
| Name | Scope | Description |
|---|---|---|
| #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
| Name | Payload | Description |
|---|---|---|
| @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 | ( | 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.