Fullscreen Menu
A Nike-style fullscreen overlay menu — the backdrop unveils from the top down and the menu items slide up from under a mask in a staggered cascade.
This is a WebberUI Pro component
Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.
npx shadcn@latest add "https://webberui.com/r/fullscreen-menu.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<FullscreenMenu />
Installation
npx shadcn@latest add "https://webberui.com/r/fullscreen-menu.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/fullscreen-menu.
Usage
import {
FullscreenMenu,
FullscreenMenuTrigger,
FullscreenMenuContent,
FullscreenMenuItem,
} from "@/components/ui/fullscreen-menu";
<FullscreenMenu>
<FullscreenMenuTrigger>Open menu</FullscreenMenuTrigger>
<FullscreenMenuContent>
<FullscreenMenuItem href="#men" index="01">
Men
</FullscreenMenuItem>
<FullscreenMenuItem href="#women" index="02">
Women
</FullscreenMenuItem>
<FullscreenMenuItem href="#sport" index="03">
Sport collection
</FullscreenMenuItem>
</FullscreenMenuContent>
</FullscreenMenu>;Clicking the trigger unveils the backdrop downward from the top of the screen, and then each FullscreenMenuItem slides up from under the mask in turn. Clicking the backdrop, pressing the close button in the top-right corner, or pressing Esc all close it; selecting an item closes it automatically by default.
FullscreenMenuItem renders as an <a> when it has an href and a <button> otherwise, and it can be paired with onSelect to handle selection:
<FullscreenMenuItem onSelect={() => router.push("/men")} index="01">
Men
</FullscreenMenuItem>;Passing open and onOpenChange switches it to controlled mode (for example so an external hamburger button can drive it):
const [open, setOpen] = React.useState(false);
<FullscreenMenu open={open} onOpenChange={setOpen}>
{/* ... */}
</FullscreenMenu>;Props
FullscreenMenu
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | The content, which must include FullscreenMenuTrigger and FullscreenMenuContent |
open | boolean | — | Open state in controlled mode; when omitted, the component manages it internally |
onOpenChange | (open: boolean) => void | — | Callback fired when the open state changes (in both controlled and uncontrolled modes) |
FullscreenMenuTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Trigger content |
className | string | — | Appended to the trigger's className |
FullscreenMenuContent
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Overlay content, usually several FullscreenMenuItem elements |
align | "start" | "center" | "start" | Horizontal alignment of the menu items |
label | string | "Main menu" | Accessible label for the overlay (applied to role="dialog") |
className | string | — | Appended to the overlay container's className |
FullscreenMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Item text |
href | string | — | When set, renders as an <a>; otherwise a <button> |
index | string | — | Optional number label (such as "01"), shown before the item |
onSelect | () => void | — | Callback fired when the item is clicked and selected |
closeOnSelect | boolean | true | Whether selecting closes the menu automatically |
className | string | — | Appended to the item link's className |
FullscreenMenuClose
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | lucide-react X icon | Custom button content |
className | string | — | Appended to the close button's className |
How it works
- A staggered reveal: the list container uses
staggerChildrento offset each item's trigger time, and every item sits inside a staticoverflow-hiddenmask while only the inner link does theydisplacement — so the text appears to "rise" from the edge of the mask, giving the clean Nike-style cropped edge. On exit,staggerDirection: -1collapses them in reverse. - A top-down backdrop: the backdrop unveils downward from the top of the screen with
scaleY(transformOrigin: top), and on exit it waits a moment before retracting, so the items leave first and the backdrop closes last. - Portaled to body: the overlay and the backdrop are rendered into
document.bodyviacreatePortal, which avoids an ancestor'soverflowclipping them or atransformcreating a containing block that would breakfixedpositioning. - Layered pointer-events: the dialog layer is
pointer-events-noneby default so clicks outside the overlay land on the backdrop (which closes it), and only interactive elements such as links and the close button restorepointer-eventson themselves. - controlled mode: passing
openswitches it to controlled and the component stops changing its internal state;onOpenChangefires in both controlled and uncontrolled modes, so you can keep it in sync with external navigation state. - Tuning points: hover and icon transition durations read
var(--wb-duration-fast, 200ms)and the easing readsvar(--wb-ease-out, cubic-bezier(0.22,1,0.36,1)), falling back to the defaults when undefined.
Accessibility
- The overlay carries
role="dialog",aria-modal="true", and anaria-label; focus moves into the overlay when it opens (tabIndex={-1}), and after closing focus returns to the trigger - The trigger is a native
<button>carryingaria-haspopup="dialog"and thearia-expandedstate, linked to the overlay witharia-controlswhile open - The menu is carried by a semantic
<nav>with<ul>/<li>structure, and items render as<a>or<button>depending onhref, all focusable and operable by keyboard - Pressing Esc or clicking the backdrop closes it; while open,
bodyis set tooverflow: hiddento lock background scrolling, and the original value is restored on close - When the user has "reduce motion" enabled at the system level, the backdrop and the items degrade to a quick fade in and out with no sense of displacement
- The component takes the lightweight route and has no built-in focus trap; if the overlay contains many interactive elements and needs a complete keyboard focus cycle, pair it with a focus trap solution of your own