WebberUI

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.

How to install Pro components →See the plans →

Loading preview…
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

PropTypeDefaultDescription
childrenReact.ReactNodeThe content, which must include FullscreenMenuTrigger and FullscreenMenuContent
openbooleanOpen state in controlled mode; when omitted, the component manages it internally
onOpenChange(open: boolean) => voidCallback fired when the open state changes (in both controlled and uncontrolled modes)

FullscreenMenuTrigger

PropTypeDefaultDescription
childrenReact.ReactNodeTrigger content
classNamestringAppended to the trigger's className

FullscreenMenuContent

PropTypeDefaultDescription
childrenReact.ReactNodeOverlay content, usually several FullscreenMenuItem elements
align"start" | "center""start"Horizontal alignment of the menu items
labelstring"Main menu"Accessible label for the overlay (applied to role="dialog")
classNamestringAppended to the overlay container's className

FullscreenMenuItem

PropTypeDefaultDescription
childrenReact.ReactNodeItem text
hrefstringWhen set, renders as an <a>; otherwise a <button>
indexstringOptional number label (such as "01"), shown before the item
onSelect() => voidCallback fired when the item is clicked and selected
closeOnSelectbooleantrueWhether selecting closes the menu automatically
classNamestringAppended to the item link's className

FullscreenMenuClose

PropTypeDefaultDescription
childrenReact.ReactNodelucide-react X iconCustom button content
classNamestringAppended to the close button's className

How it works

  • A staggered reveal: the list container uses staggerChildren to offset each item's trigger time, and every item sits inside a static overflow-hidden mask while only the inner link does the y displacement — so the text appears to "rise" from the edge of the mask, giving the clean Nike-style cropped edge. On exit, staggerDirection: -1 collapses 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.body via createPortal, which avoids an ancestor's overflow clipping them or a transform creating a containing block that would break fixed positioning.
  • Layered pointer-events: the dialog layer is pointer-events-none by default so clicks outside the overlay land on the backdrop (which closes it), and only interactive elements such as links and the close button restore pointer-events on themselves.
  • controlled mode: passing open switches it to controlled and the component stops changing its internal state; onOpenChange fires 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 reads var(--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 an aria-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> carrying aria-haspopup="dialog" and the aria-expanded state, linked to the overlay with aria-controls while open
  • The menu is carried by a semantic <nav> with <ul>/<li> structure, and items render as <a> or <button> depending on href, all focusable and operable by keyboard
  • Pressing Esc or clicking the backdrop closes it; while open, body is set to overflow: hidden to 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

On this page