UI
Overview28

Marketing7

Flick Cards SliderNewMenu NotchNewRatingNewFlip ButtonNewContact LensTimelineMagnetic Button

Interface21

Command PaletteNewContext MenuNewDynamic IslandNewMorphing PopoverNewTabsNewExpand TabsStardust SliderBadgeImage GenerationKPI ChartCopy ButtonNav NotchLiquid GlassUpdatedSliderFile UploadUpdatedGoo SwitchInline Time EditDate Range PickerUpdatedChart CardUpdatedDelete ButtonMenu DockUpdated
your-componentYouRequest a componentCan’t find it? Ask for it.

Menu

  • AboutAbout
  • PricingPricing
  • ResourcesResources
  • Our featuresOur features

Legal

  • Privacy PolicyPrivacy Policy
  • Terms & ConditionsTerms & Conditions

Social media

Start open
Theme button
ComponentsMenu Notch

Menu Notch

New

A pill with a menu toggle and a theme button that unfolds into a panel of grouped links; its icon crosses into an X, its label blurs in letter by letter and every link rolls to its hover color.

Install it in one line

Available in v1.9.0

$npx devignerui add menu-notch

Writes components/ui/menu-notch.tsx and lists what it needs.

First time here

$npx devignerui init

Or by hand

  1. Needs React 18 or 19 and Tailwind CSS v4.
  2. Install the dependencies.

    $npm i cn motion @devigner-ui/icons devignerui
  3. Paste the source from into components/ui/menu-notch.tsx.

Some of these designs take cues from interfaces we've admired around the web. If one started as your idea and you'd like credit, message us on X @devignerui or in the Discord and we'll add it.

Usage

import { useState } from "react";
import { MenuNotch, type MenuNotchSection, type MenuNotchSocial, type MenuNotchTheme } from "@/components/ui/menu-notch";
import { IconInstagram, IconYoutube } from "@devigner-ui/icons";

const sections: MenuNotchSection[] = [
  {
    label: "Menu",
    size: "lg",
    links: [
      { label: "About", href: "/about" },
      { label: "Pricing", href: "/pricing" },
      { label: "Resources", href: "/resources" },
      { label: "Our features", href: "/features" },
    ],
  },
  {
    label: "Legal",
    links: [
      { label: "Privacy Policy", href: "/privacy" },
      { label: "Terms & Conditions", href: "/terms" },
    ],
  },
];

const socials: MenuNotchSocial[] = [
  { label: "Instagram", href: "https://instagram.com", icon: <IconInstagram /> },
  { label: "YouTube", href: "https://youtube.com", icon: <IconYoutube /> },
];

export function Example() {
  const [theme, setTheme] = useState<MenuNotchTheme>("light");
  return (
    <MenuNotch
      sections={sections}
      socials={socials}
      defaultOpen
      theme={theme}
      onThemeChange={setTheme}
    />
  );
}

Custom composition

<MenuNotch sections={sections} theme={theme} onThemeChange={setTheme} className="w-120" />

Site menu wired to a theme

Pass your theme state so the round button shows the right icon and switches it.

import { useState } from "react";
import { MenuNotch, type MenuNotchSection, type MenuNotchTheme } from "@/components/ui/menu-notch";

const sections: MenuNotchSection[] = [
  {
    label: "Menu",
    size: "lg",
    links: [
      { label: "About", href: "/about" },
      { label: "Pricing", href: "/pricing" },
      { label: "Resources", href: "/resources" },
    ],
  },
  { label: "Legal", links: [{ label: "Privacy Policy", href: "/privacy" }, { label: "Terms & Conditions", href: "/terms" }] },
];

export function Header() {
  const [theme, setTheme] = useState<MenuNotchTheme>("light");
  return (
    <header className={theme === "dark" ? "dark" : undefined}>
      <MenuNotch sections={sections} theme={theme} onThemeChange={setTheme} />
    </header>
  );
}

Close the menu after navigating

Control open so a client-side navigation also folds the panel away.

import { useState } from "react";
import { MenuNotch } from "@/components/ui/menu-notch";

export function AppMenu({ go }: { go: (path: string) => void }) {
  const [open, setOpen] = useState(false);
  const link = (label: string, path: string) => ({
    label,
    onSelect: () => {
      go(path);
      setOpen(false);
    },
  });
  return (
    <MenuNotch
      open={open}
      onOpenChange={setOpen}
      sections={[{ label: "Menu", size: "lg", links: [link("Inbox", "/inbox"), link("Settings", "/settings")] }]}
    />
  );
}

Every prop

sections-
MenuNotchSection[]

Groups of links, in order: { label, links, size? }. Each link is { label, href?, onSelect? }: a link with href, else a button. size "lg" shows 22.5px links with a divider below (the main links); "sm" (the default) shows 16.5px links.

socials-
MenuNotchSocial[]

Icon links in a row at the bottom, sized 20px: { label, href, icon }. label is read by screen readers.

socialsLabelSocial media
string

Heading above the social icons.

open-
boolean

Controlled open state. Pass with onOpenChange.

defaultOpenfalse
boolean

Open on first render when uncontrolled.

onOpenChange-
(open: boolean) => void

Called when the toggle, Escape or a click outside opens or closes the menu.

theme"light"
"light" | "dark"

Theme the round button shows: a sun for light, a moon for dark, morphing between them when it changes. Pass your site's current theme.

onThemeChange-
(theme: "light" | "dark") => void

Shows the sun/moon button and is called with the theme to switch to. Without it the button is hidden.

menuLabelMenu
string

Toggle text while the menu is closed.

closeLabelClose
string

Toggle text while the menu is open.

classNames-
MenuNotchClassNames

Slot classes: bar (the pill), panel (the surface that grows open) and link (every link and social icon).

classNamew-103.5
string

Classes on the menu. Its width is the panel's width, 414px by default.

How to use it

The live demo is a site menu: a pill with Menu and a sun button. Open it and the panel unfolds under the pill while the two lines cross into an X and Close blurs in letter by letter; run the pointer down the links and each one rolls up to a tinted copy, slow off the mark and then gliding in. The sun button switches the page between light and dark, its sun morphing into a moon.

When to use it

  • A compact site or app menu that sits at the top of the page and opens over the content, with a handful of main links and a few secondary ones.
  • Landing pages and portfolios where the menu itself should feel crafted: the theme button comes with it.

When to reach for something else

  • Deep navigation with nested levels or dozens of links. Use a sidebar or a full menu page.
  • Actions on a selection, such as Rename or Delete. Use Context Menu or Command Palette.

Keyboard

Tab
Moves through the toggle, the theme button, then every link while the menu is open.
Enter / Space
Opens or closes the menu from the toggle, and follows a focused link.
Escape
Closes the menu.

Accessibility

  • The menu is a nav landmark named "Main"; pass aria-label to rename it.
  • The toggle is a button with aria-expanded and aria-controls pointing at the panel, and is named by its current label (Menu or Close). The letters themselves are hidden from screen readers.
  • While closed the panel is inert and aria-hidden, so its links are out of the tab order.
  • Each group is a list labelled by its heading. Social links are named by their label, since they only show an icon.
  • A link rolls to its tinted copy on keyboard focus as well as on hover; the copy is aria-hidden.
  • Honors prefers-reduced-motion and an ancestor <MotionConfig reducedMotion="always">, so an in-app motion switch works too.

Theming

  • The pill is foreground with background text, so it is dark on a light page and light on a dark one, and the panel is background. Section headings use muted-foreground and the divider is foreground at 8%.
  • Links and icons tint to primary on hover and focus, fading over 400ms on ease-tint. Change it through classNames.link, such as hover:text-destructive focus-visible:text-destructive.
  • Panel, pill and headings fade over 500ms when the theme changes (the pill on ease-out, the rest on ease-tint), so a theme switch crossfades the whole menu. A theme switch run inside a view transition replaces this with the transition's own animation.
  • The panel opens on SPRING_PILL, which also turns the icon; links roll over 600ms on EASE_ROLL, and the toggle's letters sharpen out of a 3px blur on TWEEN_LETTER, 50ms apart.
  • className sizes the menu: w-103.5 (414px) by default.

Edge cases

  • The menu takes 78px of height in the layout; the panel opens over whatever is below it, so give it a positioned spot near the top of the page.
  • Clicking outside the menu or pressing Escape closes it.
  • Without onThemeChange the sun/moon button is not rendered. theme only picks the icon: switching your site's theme is up to onThemeChange.
  • Only sections with size "lg" get a divider below them.

Troubleshooting

The panel opens behind other content.

The panel is absolutely positioned with z-50 inside the menu. If a parent creates its own stacking context with a lower z-index, raise that parent, or the menu itself with className="z-50".

The sun/moon button doesn't show.

It only renders when onThemeChange is passed. Wire it to your theme state, and pass theme so it shows the right icon.

The component renders with no background or the wrong colors.

Colors use the standard shadcn/ui tokens only (background, foreground, primary, secondary, muted, accent, border, input, ring, destructive), so a project set up with shadcn/ui needs nothing extra and the component follows its theme, dark mode included. Without shadcn/ui, define those CSS variables for :root and .dark and map them in your Tailwind v4 @theme, or run npx shadcn init.

Stuck on something else? Ask in the Discord.