Menubar
A visually persistent menu common in desktop applications that provides quick access to a consistent set of commands.
<script lang="ts">
import * as Menubar from "$lib/components/ui/menubar/index.js";
let bookmarks = $state(false);
let fullUrls = $state(true);
let profileRadioValue = $state("benoit");
</script>
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger>File</Menubar.Trigger>
<Menubar.Content>
<Menubar.Item>
New Tab <Menubar.Shortcut>⌘T</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Item>
New Window <Menubar.Shortcut>⌘N</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Item>New Incognito Window</Menubar.Item>
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>Share</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.Item>Email link</Menubar.Item>
<Menubar.Item>Messages</Menubar.Item>
<Menubar.Item>Notes</Menubar.Item>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Separator />
<Menubar.Item>
Print... <Menubar.Shortcut>⌘P</Menubar.Shortcut>
</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger>Edit</Menubar.Trigger>
<Menubar.Content>
<Menubar.Item>
Undo <Menubar.Shortcut>⌘Z</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Item>
Redo <Menubar.Shortcut>⇧⌘Z</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>Find</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.Item>Search the web</Menubar.Item>
<Menubar.Separator />
<Menubar.Item>Find...</Menubar.Item>
<Menubar.Item>Find Next</Menubar.Item>
<Menubar.Item>Find Previous</Menubar.Item>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Separator />
<Menubar.Item>Cut</Menubar.Item>
<Menubar.Item>Copy</Menubar.Item>
<Menubar.Item>Paste</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger>View</Menubar.Trigger>
<Menubar.Content>
<Menubar.CheckboxItem bind:checked={bookmarks}
>Always Show Bookmarks Bar</Menubar.CheckboxItem
>
<Menubar.CheckboxItem bind:checked={fullUrls}
>Always Show Full URLs</Menubar.CheckboxItem
>
<Menubar.Separator />
<Menubar.Item inset>
Reload <Menubar.Shortcut>⌘R</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Item inset>
Force Reload <Menubar.Shortcut>⇧⌘R</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item inset>Toggle Fullscreen</Menubar.Item>
<Menubar.Separator />
<Menubar.Item inset>Hide Sidebar</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger>Profiles</Menubar.Trigger>
<Menubar.Content>
<Menubar.RadioGroup bind:value={profileRadioValue}>
<Menubar.RadioItem value="andy">Andy</Menubar.RadioItem>
<Menubar.RadioItem value="benoit">Benoit</Menubar.RadioItem>
<Menubar.RadioItem value="Luis">Luis</Menubar.RadioItem>
</Menubar.RadioGroup>
<Menubar.Separator />
<Menubar.Item inset>Edit...</Menubar.Item>
<Menubar.Separator />
<Menubar.Item inset>Add Profile...</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
</Menubar.Root> Installation
pnpm dlx shadcn-svelte@latest add menubar Install bits-ui:
pnpm add bits-ui -D Copy and paste the following code into your project.
import Root from './menubar.svelte';
import Menu from './menubar-menu.svelte';
import Sub from './menubar-sub.svelte';
import RadioGroup from './menubar-radio-group.svelte';
import CheckboxItem from './menubar-checkbox-item.svelte';
import Content from './menubar-content.svelte';
import Item from './menubar-item.svelte';
import Group from './menubar-group.svelte';
import RadioItem from './menubar-radio-item.svelte';
import Separator from './menubar-separator.svelte';
import Shortcut from './menubar-shortcut.svelte';
import SubContent from './menubar-sub-content.svelte';
import SubTrigger from './menubar-sub-trigger.svelte';
import Trigger from './menubar-trigger.svelte';
import Label from './menubar-label.svelte';
import GroupHeading from './menubar-group-heading.svelte';
import Portal from './menubar-portal.svelte';
export {
Root,
CheckboxItem,
Content,
Item,
RadioItem,
Separator,
Shortcut,
SubContent,
SubTrigger,
Trigger,
Menu,
Group,
Sub,
RadioGroup,
Label,
GroupHeading,
Portal,
//
Root as Menubar,
CheckboxItem as MenubarCheckboxItem,
Content as MenubarContent,
Item as MenubarItem,
RadioItem as MenubarRadioItem,
Separator as MenubarSeparator,
Shortcut as MenubarShortcut,
SubContent as MenubarSubContent,
SubTrigger as MenubarSubTrigger,
Trigger as MenubarTrigger,
Menu as MenubarMenu,
Group as MenubarGroup,
Sub as MenubarSub,
RadioGroup as MenubarRadioGroup,
Label as MenubarLabel,
GroupHeading as MenubarGroupHeading,
Portal as MenubarPortal
};
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn, type WithoutChildrenOrChild } from '$UTILS$.js';
import type { Snippet } from 'svelte';
import MinusIcon from 'phosphor-svelte/lib/Minus';
import CheckIcon from 'phosphor-svelte/lib/Check';
let {
ref = $bindable(null),
class: className,
checked = $bindable(false),
indeterminate = $bindable(false),
inset,
children: childrenProp,
...restProps
}: WithoutChildrenOrChild<MenubarPrimitive.CheckboxItemProps> & {
inset?: boolean;
children?: Snippet;
} = $props();
</script>
<MenubarPrimitive.CheckboxItem
bind:ref
bind:checked
bind:indeterminate
data-slot="menubar-checkbox-item"
data-inset={inset}
class={cn(
'relative flex cursor-default items-center gap-2.5 border border-transparent py-2 pr-3 pl-9.5 font-mono text-xs font-medium uppercase tracking-[0.04em] text-zinc-300 outline-hidden select-none focus:border-[#b9d765]/50 focus:bg-[#18181b] focus:text-zinc-50 focus:**:text-[#d0e891] data-[checked=true]:text-[#d0e891] data-inset:pl-9.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
className
)}
{...restProps}
>
{#snippet children({ checked: checked, indeterminate: indeterminate })}
<span
class="pointer-events-none absolute left-3 flex size-4 items-center justify-center text-[#d0e891] [&_svg:not([class*='size-'])]:size-4"
>
{#if indeterminate}
<MinusIcon />
{:else if checked}
<CheckIcon />
{/if}
</span>
{@render childrenProp?.()}
{/snippet}
</MenubarPrimitive.CheckboxItem>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import MenubarPortal from './menubar-portal.svelte';
import { cn, type WithoutChildrenOrChild } from '$UTILS$.js';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
class: className,
sideOffset = 8,
alignOffset = -4,
align = 'start',
side = 'bottom',
portalProps,
...restProps
}: MenubarPrimitive.ContentProps & {
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof MenubarPortal>>;
} = $props();
</script>
<MenubarPortal {...portalProps}>
<MenubarPrimitive.Content
bind:ref
data-slot="menubar-content"
{align}
{alignOffset}
{side}
{sideOffset}
class={cn(
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 z-50 min-w-36 origin-(--bits-menubar-content-transform-origin) overflow-hidden border border-[#222225] bg-[#09090b] p-1 text-zinc-50 shadow-none duration-100',
className
)}
{...restProps}
/>
</MenubarPortal>
<script lang="ts">
import { cn } from '$UTILS$.js';
import { Menubar as MenubarPrimitive } from 'bits-ui';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
inset,
class: className,
...restProps
}: ComponentProps<typeof MenubarPrimitive.GroupHeading> & {
inset?: boolean;
} = $props();
</script>
<MenubarPrimitive.GroupHeading
bind:ref
data-slot="menubar-group-heading"
data-inset={inset}
class={cn(
'px-2 py-1.5 font-mono text-xs font-semibold uppercase tracking-[0.08em] text-zinc-400 data-[inset]:ps-8',
className
)}
{...restProps}
/>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.GroupProps & {
inset?: boolean;
} = $props();
</script>
<MenubarPrimitive.Group
bind:ref
data-slot="menubar-group"
class={cn('border-border/60 py-1 not-first:border-t', className)}
{...restProps}
/>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
inset = undefined,
variant = 'default',
...restProps
}: MenubarPrimitive.ItemProps & {
inset?: boolean;
variant?: 'default' | 'destructive';
} = $props();
</script>
<MenubarPrimitive.Item
bind:ref
data-slot="menubar-item"
data-inset={inset}
data-variant={variant}
class={cn(
"flex cursor-default items-center gap-2.5 border border-transparent px-3 py-2 font-mono text-xs font-medium uppercase tracking-[0.04em] text-zinc-300 outline-hidden select-none focus:border-[#b9d765]/50 focus:bg-[#18181b] focus:text-zinc-50 data-[variant=destructive]:text-[#f85149] data-[variant=destructive]:focus:bg-[#d1242f]/10 data-[variant=destructive]:focus:text-[#f85149] data-[variant=destructive]:*:[svg]:text-[#f85149]! not-data-[variant=destructive]:focus:**:text-[#d0e891] data-disabled:opacity-50 data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-3.5 group/menubar-item",
className
)}
{...restProps}
/>
<script lang="ts">
import { cn } from '$UTILS$.js';
import { type WithElementRef } from 'bits-ui';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
inset,
children,
class: className,
...restProps
}: WithElementRef<HTMLAttributes<HTMLElement>> & {
inset?: boolean;
} = $props();
</script>
<div
bind:this={ref}
data-slot="menubar-label"
data-inset={inset}
class={cn(
'px-3.5 py-2 font-mono text-xs font-semibold uppercase tracking-[0.08em] text-zinc-500 data-inset:pl-9.5',
className
)}
{...restProps}
>
{@render children?.()}
</div>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
let { ...restProps }: MenubarPrimitive.MenuProps = $props();
</script>
<MenubarPrimitive.Menu {...restProps} />
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
let { ...restProps }: MenubarPrimitive.PortalProps = $props();
</script>
<MenubarPrimitive.Portal {...restProps} />
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
value = $bindable(''),
class: className,
...restProps
}: MenubarPrimitive.RadioGroupProps = $props();
</script>
<MenubarPrimitive.RadioGroup
bind:ref
bind:value
data-slot="menubar-radio-group"
class={cn('border-border/60 py-1 not-first:border-t', className)}
{...restProps}
/>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn, type WithoutChild } from '$UTILS$.js';
import CheckIcon from 'phosphor-svelte/lib/Check';
let {
ref = $bindable(null),
class: className,
inset,
children: childrenProp,
...restProps
}: WithoutChild<MenubarPrimitive.RadioItemProps> & {
inset?: boolean;
} = $props();
</script>
<MenubarPrimitive.RadioItem
bind:ref
data-slot="menubar-radio-item"
data-inset={inset}
class={cn(
"relative flex cursor-default items-center gap-2.5 border border-transparent py-2 pr-3 pl-9.5 font-mono text-xs font-medium uppercase tracking-[0.04em] text-zinc-300 outline-hidden select-none focus:border-[#b9d765]/50 focus:bg-[#18181b] focus:text-zinc-50 focus:**:text-[#d0e891] data-[checked=true]:text-[#d0e891] data-inset:pl-9.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
className
)}
{...restProps}
>
{#snippet children({ checked })}
<span
class="pointer-events-none absolute left-3 flex size-4 items-center justify-center text-[#d0e891] [&_svg:not([class*='size-'])]:size-4"
>
{#if checked}
<CheckIcon />
{/if}
</span>
{@render childrenProp?.({ checked })}
{/snippet}
</MenubarPrimitive.RadioItem>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.SeparatorProps = $props();
</script>
<MenubarPrimitive.Separator
bind:ref
data-slot="menubar-separator"
class={cn('-mx-1.5 my-1.5 h-px bg-[#222225]', className)}
{...restProps}
/>
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
data-slot="menubar-shortcut"
class={cn(
'ml-auto font-mono text-[11px] uppercase tracking-[0.12em] text-zinc-500 group-focus/menubar-item:text-[#d0e891]',
className
)}
{...restProps}
>
{@render children?.()}
</span>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.SubContentProps = $props();
</script>
<MenubarPrimitive.SubContent
bind:ref
data-slot="menubar-sub-content"
class={cn(
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 min-w-32 border border-[#222225] bg-[#09090b] p-1.5 text-zinc-50 shadow-none duration-100',
className
)}
{...restProps}
/>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn, type WithoutChild } from '$UTILS$.js';
import CaretRightIcon from 'phosphor-svelte/lib/CaretRight';
let {
ref = $bindable(null),
class: className,
inset = undefined,
children,
...restProps
}: WithoutChild<MenubarPrimitive.SubTriggerProps> & {
inset?: boolean;
} = $props();
</script>
<MenubarPrimitive.SubTrigger
bind:ref
data-slot="menubar-sub-trigger"
data-inset={inset}
class={cn(
"flex cursor-default items-center gap-2 border border-transparent px-3 py-2 font-mono text-xs font-medium uppercase tracking-[0.04em] text-zinc-300 outline-none select-none focus:border-[#b9d765]/50 focus:bg-[#18181b] focus:text-zinc-50 data-open:border-[#b9d765]/50 data-open:bg-[#18181b] data-open:text-zinc-50 data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-3.5",
className
)}
{...restProps}
>
{@render children?.()}
<CaretRightIcon class="cn-rtl-flip ml-auto size-4 text-[#d0e891]" />
</MenubarPrimitive.SubTrigger>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: MenubarPrimitive.SubProps = $props();
</script>
<MenubarPrimitive.Sub bind:open {...restProps} />
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.TriggerProps = $props();
</script>
<MenubarPrimitive.Trigger
bind:ref
data-slot="menubar-trigger"
class={cn(
'flex items-center rounded-full px-3 py-1 font-mono text-xs font-medium uppercase tracking-[0.04em] text-zinc-400 outline-hidden select-none transition-colors hover:bg-zinc-50 hover:text-zinc-950 aria-expanded:bg-zinc-50 aria-expanded:text-zinc-950',
className
)}
{...restProps}
/>
<script lang="ts">
import { Menubar as MenubarPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: MenubarPrimitive.RootProps = $props();
</script>
<MenubarPrimitive.Root
bind:ref
data-slot="menubar"
class={cn(
'flex h-10 items-center gap-0.5 border border-[#222225] bg-[#09090b] p-1 text-zinc-50 shadow-none',
className
)}
{...restProps}
/>
Usage
<script lang="ts">
import * as Menubar from '$lib/components/ui/menubar/index.js';
</script> <Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger>File</Menubar.Trigger>
<Menubar.Content>
<Menubar.Item>
New Tab
<Menubar.Shortcut>⌘T</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Item>New Window</Menubar.Item>
<Menubar.Separator />
<Menubar.Item>Share</Menubar.Item>
<Menubar.Separator />
<Menubar.Item>Print</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
</Menubar.Root>