Skip to content

Hamburger Menu

A compact mobile navigation bar with simple links or expandable groups.

---
import { Button } from '@bejamas/registry/ui/button';
import { HamburgerMenu, HamburgerMenuItem } from '@bejamas/registry/ui/hamburger-menu';
import { SemanticIcon } from '@bejamas/registry/ui/icon';
---
<div class="relative w-full max-w-[375px] self-start overflow-hidden border border-border [--hamburger-menu-panel-height:272px] [&:has([data-state=open])]:h-[320px]">
<HamburgerMenu closeOnOutsideClick={false}>
<span slot="brand" class="flex h-8 w-[100px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</span>
<Button slot="actions" type="button" variant="secondary" size="icon" aria-label="Toggle theme"><SemanticIcon name="sun" /></Button>
<Button slot="actions" as="a" href="https://github.com/bejamas" variant="secondary" aria-label="Bejamas on GitHub, 220 stars"><img class="dark:brightness-0 dark:invert" src="/figma/hamburger-menu/github-header.svg" width="14" height="14" alt="" />220</Button>
<HamburgerMenuItem href="#getting-started">Getting started</HamburgerMenuItem>
<HamburgerMenuItem href="#components">Components</HamburgerMenuItem>
</HamburgerMenu>
</div>
Terminal window
bunx bejamas add hamburger-menu
---
import { HamburgerMenu, HamburgerMenuItem, HamburgerMenuGroup } from '@bejamas/registry/ui/hamburger-menu';
---
<HamburgerMenu>
<a slot="brand" href="/">Bejamas UI</a>
<HamburgerMenuGroup label="Getting started" defaultOpen>
<HamburgerMenuItem href="/introduction">Introduction</HamburgerMenuItem>
<HamburgerMenuItem href="/installation">Installation</HamburgerMenuItem>
</HamburgerMenuGroup>
</HamburgerMenu>
Prop Type Default
class string ""
defaultOpen boolean false
closeOnOutsideClick boolean true
label string "Mobile navigation"
triggerLabel string "Menu"
---
import { Button } from '@bejamas/registry/ui/button';
import { HamburgerMenu, HamburgerMenuItem } from '@bejamas/registry/ui/hamburger-menu';
import { SemanticIcon } from '@bejamas/registry/ui/icon';
---
<div class="relative w-full max-w-[375px] self-start overflow-hidden border border-border [--hamburger-menu-panel-height:688px] [&:has([data-state=open])]:h-[736px]">
<HamburgerMenu closeOnOutsideClick={false}>
<span slot="brand" class="flex h-8 w-[100px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</span>
<Button slot="actions" type="button" variant="secondary" size="icon" aria-label="Toggle theme"><SemanticIcon name="sun" /></Button>
<Button slot="actions" as="a" href="https://github.com/bejamas" variant="secondary" aria-label="Bejamas on GitHub, 220 stars"><img class="dark:brightness-0 dark:invert" src="/figma/hamburger-menu/github-header.svg" width="14" height="14" alt="" />220</Button>
<HamburgerMenuItem href="#getting-started">Getting started</HamburgerMenuItem>
</HamburgerMenu>
</div>
---
import { Button } from '@bejamas/registry/ui/button';
import { HamburgerMenu, HamburgerMenuItem } from '@bejamas/registry/ui/hamburger-menu';
import { SemanticIcon } from '@bejamas/registry/ui/icon';
---
<div class="relative w-full max-w-[375px] self-start overflow-hidden border border-border [--hamburger-menu-panel-height:688px] [&:has([data-state=open])]:h-[736px]">
<HamburgerMenu defaultOpen closeOnOutsideClick={false}>
<span slot="brand" class="flex h-8 w-[100px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</span>
<Button slot="actions" type="button" variant="secondary" size="icon" aria-label="Toggle theme"><SemanticIcon name="sun" /></Button>
<Button slot="actions" as="a" href="https://github.com/bejamas" variant="secondary" aria-label="Bejamas on GitHub, 220 stars"><img class="dark:brightness-0 dark:invert" src="/figma/hamburger-menu/github-header.svg" width="14" height="14" alt="" />220</Button>
<HamburgerMenuItem href="#getting-started">Getting started</HamburgerMenuItem>
<HamburgerMenuItem href="#installation">Installation</HamburgerMenuItem>
<HamburgerMenuItem href="#components">Components</HamburgerMenuItem>
<HamburgerMenuItem href="#concepts">Concepts</HamburgerMenuItem>
<HamburgerMenuItem href="#blog">Blog</HamburgerMenuItem>
<HamburgerMenuItem href="#about">About us</HamburgerMenuItem>
<HamburgerMenuItem href="#contact">Contact us</HamburgerMenuItem>
<HamburgerMenuItem href="#faq">FAQ</HamburgerMenuItem>
<Button slot="footer" as="a" href="https://x.com/bejamas" variant="secondary" size="icon" aria-label="Bejamas on X"><img class="dark:brightness-0 dark:invert" src="/figma/hamburger-menu/social-x.svg" width="16" height="16" alt="" /></Button>
<Button slot="footer" as="a" href="https://github.com/bejamas" variant="secondary" size="icon" aria-label="Bejamas on GitHub"><img class="dark:brightness-0 dark:invert" src="/figma/hamburger-menu/github-footer.svg" width="16" height="16" alt="" /></Button>
</HamburgerMenu>
</div>
Slot area
---
import { Button } from '@bejamas/registry/ui/button';
import { HamburgerMenu, HamburgerMenuGroup, HamburgerMenuItem } from '@bejamas/registry/ui/hamburger-menu';
import { SemanticIcon } from '@bejamas/registry/ui/icon';
---
<div class="relative w-full max-w-[375px] self-start overflow-hidden border border-border [--hamburger-menu-panel-height:688px] [&:has([data-state=open])]:h-[736px]">
<HamburgerMenu defaultOpen closeOnOutsideClick={false}>
<span slot="brand" class="flex h-8 w-[100px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</span>
<Button slot="actions" type="button" variant="secondary" size="icon" aria-label="Toggle theme"><SemanticIcon name="sun" /></Button>
<Button slot="actions" as="a" href="https://github.com/bejamas" variant="secondary" aria-label="Bejamas on GitHub, 220 stars"><img class="dark:brightness-0 dark:invert" src="/figma/hamburger-menu/github-header.svg" width="14" height="14" alt="" />220</Button>
<HamburgerMenuGroup label="Getting started" defaultOpen>
<HamburgerMenuItem href="#introduction">Introduction</HamburgerMenuItem>
<HamburgerMenuItem href="#installation-advanced">Installation</HamburgerMenuItem>
<HamburgerMenuItem href="#cli">CLI</HamburgerMenuItem>
<HamburgerMenuItem href="#forms">Forms with Astro Actions</HamburgerMenuItem>
</HamburgerMenuGroup>
<HamburgerMenuGroup label="Components"><HamburgerMenuItem href="#buttons">Buttons</HamburgerMenuItem></HamburgerMenuGroup>
<HamburgerMenuGroup label="Concepts"><HamburgerMenuItem href="#theming">Theming</HamburgerMenuItem></HamburgerMenuGroup>
<HamburgerMenuGroup label="About us"><HamburgerMenuItem href="#team">Team</HamburgerMenuItem></HamburgerMenuGroup>
<HamburgerMenuGroup label="Contact us"><HamburgerMenuItem href="#contact-details">Contact details</HamburgerMenuItem></HamburgerMenuGroup>
<Button slot="footer" as="a" href="https://x.com/bejamas" variant="secondary" size="icon" aria-label="Bejamas on X"><img class="dark:brightness-0 dark:invert" src="/figma/hamburger-menu/social-x.svg" width="16" height="16" alt="" /></Button>
<Button slot="footer" as="a" href="https://github.com/bejamas" variant="secondary" size="icon" aria-label="Bejamas on GitHub"><img class="dark:brightness-0 dark:invert" src="/figma/hamburger-menu/github-footer.svg" width="16" height="16" alt="" /></Button>
</HamburgerMenu>
</div>

The 48px header remains visible while the panel fills the rest of the viewport. Add your own controls with the brand, actions, and footer slots. For a fixed-height preview, set --hamburger-menu-panel-height on a parent element.

The trigger is named by triggerLabel and reports its state with aria-expanded. It supports mouse, touch, Enter, and Space. Escape closes the panel and returns focus to the trigger when focus was inside the menu. Clicking outside the menu or selecting a link closes the panel. Popovers and menus opened from the menu close first and count as inside it. Set closeOnOutsideClick={false} to keep the panel open while interacting with the rest of the page, for example in previews. Groups use native disclosure elements and support keyboard interaction.

The open state and dismissal are managed by @data-slot/collapsible, so the root sets data-state (open or closed), emits collapsible:change with { open: boolean }, and accepts collapsible:set with { open: boolean } for programmatic control.