suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
GitHubGet started
DocsComponentsPatternsInteractionsChartsBlocks
Components82
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Wheel Picker
  • Widget

suiss/ui · Component

Fullscreen Menu

A navigation menu that covers the whole screen, for site links on small viewports.

Fullscreen Menu is an open-source component from suiss/ui for React. The @esuiss/ui CLI copies its source into your project, so you own the code.

Fullscreen menu is built on the dialog primitive of the chosen base. The FullscreenMenu root manages the open state, and FullscreenMenuTrigger renders a menu button whose two-line icon animates into a close icon. FullscreenMenuContent covers the viewport and reveals from the top, with a sticky header that holds the close button. Inside it, FullscreenMenuTitle names the menu, FullscreenMenuGroup holds a list of FullscreenMenuLink items, and FullscreenMenuLabel adds a caption to a group. Links and labels fade in with a staggered delay as the menu opens.

Use it for the primary navigation of a website on phones, like the global navigation on apple.com. For a short list of actions, use a dropdown menu; for a panel that keeps part of the page visible, use a drawer or sheet.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/fullscreen-menu

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add fullscreen-menu

Manual

  1. Install the dependencies and add the cn helper:
$ pnpm add @base-ui/react
  1. Add the pieces it builds on: Button.

  2. Copy the source into components/ui/fullscreen-menu.tsx:

  1. Update the import paths to match your project.

Usage

import { FullscreenMenu, FullscreenMenuTrigger, FullscreenMenuContent, FullscreenMenuTitle, FullscreenMenuGroup, FullscreenMenuLabel, FullscreenMenuLink, FullscreenMenuClose } 
<MenuExample

The menu is uncontrolled by default; set defaultOpen to start open, or control it with open and onOpenChange. Choosing a FullscreenMenuLink closes the menu. Set isActive on the link for the current page.

Groups that contain a FullscreenMenuLabel render their links smaller than groups without one, so the first group reads as primary navigation. FullscreenMenuLink can render a router link instead of an anchor.

Use FullscreenMenuLink with the render prop to render a router link instead of an anchor.

Set asChild on FullscreenMenuLink and pass a router link as the child.

Pass a render function to FullscreenMenuLink that returns a router link with the props it receives.

Examples

Links only

A menu with a single group of primary links and no labelled sections.

With close label

A menu whose close button shows the text Done instead of an icon.

API Reference

01

FullscreenMenu

The root that manages the menu's open state.

PropTypeDefaultDescription
openboolean-Whether the menu is open, for controlled use.
defaultOpenbooleanfalseWhether the menu starts open when uncontrolled.
onOpenChange(open: boolean) => void-Called when the menu opens or closes.

Also accepts the props of the primitive part it wraps, plus FullscreenMenuProps.

On the aria base, use isOpen instead of open.

02

FullscreenMenuTrigger

The menu button, with an icon that animates between menu and close states.

Also accepts the props of the primitive part it wraps.

03

FullscreenMenuContent

The full-screen panel, with a sticky header that holds the close button.

PropTypeDefaultDescription
showCloseButtonbooleantrueShows the header with a close button at the top of the menu.
closeLabelstring-Replaces the close button's icon with this text.

Also accepts the props of the primitive part it wraps.

04

FullscreenMenuTitle

The menu's title, visually hidden and used as its accessible name.

Also accepts the props of the primitive part it wraps.

05

FullscreenMenuGroup

A list of links, animated in sequence.

Also accepts the props of React.ComponentProps<"div">.

06

FullscreenMenuLabel

A caption for a group of links.

Also accepts the props of React.ComponentProps<"div">.

07

FullscreenMenuLink

A navigation link that closes the menu when chosen.

PropTypeDefaultDescription
isActivebooleanfalseMarks the link as the current page with aria-current="page" and text-primary.

Also accepts the props of useRender.ComponentProps<"a">, React.ComponentProps<"a">.

08

FullscreenMenuClose

A button that closes the menu from any element.

Also accepts the props of the primitive part it wraps.

Accessibility

  • The panel has the dialog role and takes its name from FullscreenMenuTitle, which is visually hidden. Always include one.
  • The trigger has no text of its own, so give it an accessible name with aria-label. It sets aria-expanded while the menu is open.
  • Opening the menu moves focus inside it. Escape closes it and returns focus to the trigger.
  • The icon-only close button has a visually hidden Close label.
  • The active link sets aria-current="page".

Notes

  • Styling hooks: data-slot values fullscreen-menu-trigger, fullscreen-menu-content, fullscreen-menu-header, fullscreen-menu-body, fullscreen-menu-close, fullscreen-menu-title, fullscreen-menu-group, fullscreen-menu-label and fullscreen-menu-link.
  • The active link gets data-active.
  • The stagger delay is computed from a link's position in its group, for up to 10 links, and the group's position in the menu, for up to 6 sections.
  • The content respects safe-area insets, and animations are disabled when reduced motion is preferred.
  • Uses the background, foreground, muted-foreground and primary tokens.
NextHapticsPreviousField

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • Links only
    • With close label
  4. 04API Reference
    • FullscreenMenu
    • FullscreenMenuTrigger
    • FullscreenMenuContent
    • FullscreenMenuTitle
    • FullscreenMenuGroup
    • FullscreenMenuLabel
    • FullscreenMenuLink
    • FullscreenMenuClose
  5. 05Accessibility
  6. 06Notes
from
"@/components/ui/fullscreen-menu"
/>
import * as React from "react"
import {
  FullscreenMenu,
  FullscreenMenuContent,
  FullscreenMenuGroup,
  FullscreenMenuLabel,
  FullscreenMenuLink,
  FullscreenMenuTitle,
  FullscreenMenuTrigger,
} from "@/components/ui/fullscreen-menu"

const primary = ["Store", "Products", "Services", "Company", "Support"]

const sections = [
  { label: "Explore", links: ["Overview", "Compare", "Accessories"] },
  { label: "Shop", links: ["Shop online", "Find a store", "Order status"] },
]

function MenuExample({
  defaultOpen,
  closeLabel,
  withSections = true,
}: {
  defaultOpen?: boolean
  closeLabel?: string
  withSections?: boolean
}) {
  const [active, setActive] = React.useState("Store")

  return (
    <div className="flex h-14 items-center justify-between px-4">
      <span className="font-semibold">Acme</span>
      <FullscreenMenu defaultOpen={defaultOpen}>
        <FullscreenMenuTrigger aria-label="Menu" />
        <FullscreenMenuContent closeLabel={closeLabel}>
          <FullscreenMenuTitle>Menu</FullscreenMenuTitle>
          <FullscreenMenuGroup>
            {primary.map((link) => (
              <FullscreenMenuLink
                key={link}
                href={`#${link.toLowerCase()}`}
                isActive={active === link}
                onClick={() => setActive(link)}
              >
                {link}
              </FullscreenMenuLink>
            ))}
          </FullscreenMenuGroup>
          {withSections &&
            sections.map((section) => (
              <FullscreenMenuGroup key={section.label}>
                <FullscreenMenuLabel>{section.label}</FullscreenMenuLabel>
                {section.links.map((link) => (
                  <FullscreenMenuLink
                    key={link}
                    href={`#${link.toLowerCase().replaceAll(" ", "-")}`}
                  >
                    {link}
                  </FullscreenMenuLink>
                ))}
              </FullscreenMenuGroup>
            ))}
        </FullscreenMenuContent>
      </FullscreenMenu>
    </div>
  )
}

export default function FullscreenMenuDemo() {
  return <MenuExample />
}
components/ui/fullscreen-menu.tsx
"use client"

import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import * as React from "react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"

type FullscreenMenuProps = {
  open?: boolean
  defaultOpen?: boolean
  onOpenChange?: (open: boolean) => void
}

type FullscreenMenuContextProps = {
import * as React from "react"
import {
  FullscreenMenu,
  FullscreenMenuContent,
  FullscreenMenuGroup,
  FullscreenMenuLabel,
  FullscreenMenuLink,
  FullscreenMenuTitle,
  FullscreenMenuTrigger,
} from "@/components/ui/fullscreen-menu"

const primary = ["Store", "Products", "Services", "Company", "Support"]

const sections = [
  { label: "Explore", links: ["Overview", "Compare", "Accessories"] },
  { label: "Shop", links: ["Shop online", "Find a store", "Order status"] },
]

function MenuExample({
  defaultOpen,
  closeLabel,
  withSections = true,
}: {
  defaultOpen?: boolean
  closeLabel?: string
  withSections?: boolean
}) {
  const [active, setActive] = React.useState("Store")

  return (
    <div className="flex h-14 items-center justify-between px-4">
      <span className="font-semibold">Acme</span>
      <FullscreenMenu defaultOpen={defaultOpen}>
        <FullscreenMenuTrigger aria-label="Menu" />
        <FullscreenMenuContent closeLabel={closeLabel}>
          <FullscreenMenuTitle>Menu</FullscreenMenuTitle>
          <FullscreenMenuGroup>
            {primary.map((link) => (
              <FullscreenMenuLink
                key={link}
                href={`#${link.toLowerCase()}`}
                isActive={active === link}
                onClick={() => setActive(link)}
              >
                {link}
              </FullscreenMenuLink>
            ))}
          </FullscreenMenuGroup>
          {withSections &&
            sections.map((section) => (
              <FullscreenMenuGroup key={section.label}>
                <FullscreenMenuLabel>{section.label}</FullscreenMenuLabel>
                {section.links.map((link) => (
                  <FullscreenMenuLink
                    key={link}
                    href={`#${link.toLowerCase().replaceAll(" ", "-")}`}
                  >
                    {link}
                  </FullscreenMenuLink>
                ))}
              </FullscreenMenuGroup>
            ))}
        </FullscreenMenuContent>
      </FullscreenMenu>
    </div>
  )
}

export default function FullscreenMenuLinksOnly() {
  return <MenuExample withSections={false} />
}
import * as React from "react"
import {
  FullscreenMenu,
  FullscreenMenuContent,
  FullscreenMenuGroup,
  FullscreenMenuLabel,
  FullscreenMenuLink,
  FullscreenMenuTitle,
  FullscreenMenuTrigger,
} from "@/components/ui/fullscreen-menu"

const primary = ["Store", "Products", "Services", "Company", "Support"]

const sections = [
  { label: "Explore", links: ["Overview", "Compare", "Accessories"] },
  { label: "Shop", links: ["Shop online", "Find a store", "Order status"] },
]

function MenuExample({
  defaultOpen,
  closeLabel,
  withSections = true,
}: {
  defaultOpen?: boolean
  closeLabel?: string
  withSections?: boolean
}) {
  const [active, setActive] = React.useState("Store")

  return (
    <div className="flex h-14 items-center justify-between px-4">
      <span className="font-semibold">Acme</span>
      <FullscreenMenu defaultOpen={defaultOpen}>
        <FullscreenMenuTrigger aria-label="Menu" />
        <FullscreenMenuContent closeLabel={closeLabel}>
          <FullscreenMenuTitle>Menu</FullscreenMenuTitle>
          <FullscreenMenuGroup>
            {primary.map((link) => (
              <FullscreenMenuLink
                key={link}
                href={`#${link.toLowerCase()}`}
                isActive={active === link}
                onClick={() => setActive(link)}
              >
                {link}
              </FullscreenMenuLink>
            ))}
          </FullscreenMenuGroup>
          {withSections &&
            sections.map((section) => (
              <FullscreenMenuGroup key={section.label}>
                <FullscreenMenuLabel>{section.label}</FullscreenMenuLabel>
                {section.links.map((link) => (
                  <FullscreenMenuLink
                    key={link}
                    href={`#${link.toLowerCase().replaceAll(" ", "-")}`}
                  >
                    {link}
                  </FullscreenMenuLink>
                ))}
              </FullscreenMenuGroup>
            ))}
        </FullscreenMenuContent>
      </FullscreenMenu>
    </div>
  )
}

export default function FullscreenMenuWithCloseLabel() {
  return <MenuExample closeLabel="Done" />
}