Navigation

Menubar With Actions

A menubar with a right-hand cluster: sections on the left, a notification count and a profile menu pinned to the right edge.

  • menubar
  • profile
  • notifications
  • popover

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/menubar-008?lang=en

Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "menubar-008" (Menubar With Actions) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/menubar-008.json

Registry item: https://vibeui.ru/r/menubar-008.json
Installs to: components/vibeui/menubar-008.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A menubar with a right-hand cluster: sections on the left, a notification count and a profile menu pinned to the right edge.

An app menubar with a right-hand block: sections on the left, a notifications button with a badge and a profile menu on the right. The menus run on the HTML popover, so Escape, outside clicks and mutual closing come from the browser. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Menubar008 } from "@/components/vibeui/menubar-008"

<Menubar008 userName="Anna Krylova" unread={4} />

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-menubar-008-* palette — do not swap it for your theme tokens (bg-card, text-muted-foreground and the like)
- aligning the right-hand menus to the button's right edge: opening rightwards near the window edge clips them
- the unread count in the button's aria-label with the badge itself aria-hidden — otherwise a screen reader reads a bare number
- margin-left:auto on the right group instead of spacers: adding a section then costs no recalculation
- popover="auto": Escape, outside clicks and closing a neighbouring menu come from the browser
- the container query that hides the name in a narrow block: otherwise the right group squeezes out the sections

## 6. You may change
- the menus array: sections and their items
- userName and unread — the profile name and the unread count
- the accent through the accent prop — it colours the initials circle and the focus rings
- the contents of the profile menu and the notification list

## 7. Rules
- Initials are taken from the first letters of the name's words: one word gives one letter, three give only the first two.
- The notification glyph is a character: swap in your icon, keeping the button size.
- Without anchor positioning support the menu opens centred: check your target browsers.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/menubar-008.json
https://vibeui.ru/r/menubar-008.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-menubar-008-*. Серверный: хуков нет, все меню — popover="auto". Правая группа прижата margin-left:auto, а её меню выравниваются по правому краю кнопки через position-area:bottom span-left, иначе они вылезают за пределы окна. Число непрочитанных попадает в aria-label кнопки, сам значок скрыт от скринридера. В узком блоке имя пользователя прячется контейнерным запросом, остаётся кружок с инициалами.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { CSSProperties } from "react"

export type Menubar008Menu = {
  label: string
  items: string[]
}

export type Menubar008Props = {
  menus?: Menubar008Menu[]
  userName?: string
  unread?: number
  accent?: string
  className?: string
  style?: CSSProperties
}

// Идея компонента: строка меню с правой частью — уведомлениями и профилем.
// Разделы и профиль открываются одним и тем же механизмом (HTML popover), но
// профиль прижат к правому краю через margin-left:auto и открывается «от себя»:
// его меню выравнивается по правому краю кнопки, иначе вылезает за пределы окна.
const STYLES = `
:where([data-vibeui-block="menubar-008"]){
--vibeui-menubar-008-bg:oklch(1 0 0);
--vibeui-menubar-008-fg:oklch(0.24 0.014 265);
--vibeui-menubar-008-muted:oklch(0.58 0.014 265);
--vibeui-menubar-008-border:oklch(0.9 0.006 265);
--vibeui-menubar-008-hover:oklch(0.55 0.02 265 / 10%);
--vibeui-menubar-008-badge:oklch(0.62 0.19 26);
--vibeui-menubar-008-accent:oklch(0.55 0.2 262);
--vibeui-menubar-008-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
container-type:inline-size;
}
[data-vibeui-block="menubar-008"]{
box-sizing:border-box;width:100%;max-width:38rem;
font-family:var(--vibeui-menubar-008-font);color:var(--vibeui-menubar-008-fg);
}
[data-vibeui-block="menubar-008"] [data-part="shell"]{
box-sizing:border-box;padding:0.3125rem;
display:flex;align-items:center;gap:0.125rem;
background:var(--vibeui-menubar-008-bg);
border:1px solid var(--vibeui-menubar-008-border);border-radius:0.625rem;
}
[data-vibeui-block="menubar-008"] [data-part="slot"]{position:relative;display:flex}
[data-vibeui-block="menubar-008"] [data-part="right"]{margin-left:auto;display:flex;align-items:center;gap:0.25rem}
[data-vibeui-block="menubar-008"] [data-part="trigger"]{
appearance:none;border:0;background:none;cursor:pointer;
display:flex;align-items:center;gap:0.5rem;
height:1.875rem;padding:0 0.625rem;border-radius:0.4375rem;
font:inherit;font-size:0.8125rem;color:inherit;
}
[data-vibeui-block="menubar-008"] [data-part="trigger"]:hover{background:var(--vibeui-menubar-008-hover)}
[data-vibeui-block="menubar-008"] [data-part="trigger"]:focus-visible{outline:2px solid var(--vibeui-menubar-008-accent);outline-offset:-2px}
[data-vibeui-block="menubar-008"] [data-part="slot"]:has([data-part="menu"]:popover-open) [data-part="trigger"]{background:var(--vibeui-menubar-008-hover)}
[data-vibeui-block="menubar-008"] [data-part="bell"]{position:relative;width:1.875rem;padding:0;justify-content:center}
/* Значок с числом: сам он скрыт от скринридера, число уходит в aria-label кнопки. */
[data-vibeui-block="menubar-008"] [data-part="dot"]{
position:absolute;top:0.1875rem;right:0.1875rem;
min-width:0.875rem;height:0.875rem;padding:0 0.1875rem;box-sizing:border-box;
display:grid;place-items:center;border-radius:999px;
background:var(--vibeui-menubar-008-badge);color:oklch(1 0 0);
font-size:0.5625rem;font-variant-numeric:tabular-nums;line-height:1;
}
[data-vibeui-block="menubar-008"] [data-part="face"]{
width:1.375rem;height:1.375rem;border-radius:999px;display:grid;place-items:center;
background:var(--vibeui-menubar-008-accent);color:oklch(1 0 0);
font-size:0.625rem;font-weight:650;
}
[data-vibeui-block="menubar-008"] [data-part="who"]{max-width:8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* В узком блоке имя прячется: остаётся кружок, кнопка не выдавливает разделы. */
@container (max-width: 26rem){
[data-vibeui-block="menubar-008"] [data-part="who"]{display:none}
}
[data-vibeui-block="menubar-008"] [data-part="menu"]{
position:fixed;inset:auto;margin:0;
min-width:12rem;padding:0.25rem;box-sizing:border-box;
background:var(--vibeui-menubar-008-bg);color:var(--vibeui-menubar-008-fg);
border:1px solid var(--vibeui-menubar-008-border);border-radius:0.625rem;
font-family:var(--vibeui-menubar-008-font);
box-shadow:0 16px 36px -18px oklch(0.2 0.03 265 / 45%);
}
@supports (anchor-name: --a){
[data-vibeui-block="menubar-008"] [data-part="trigger"]{anchor-name:var(--vibeui-menubar-008-anchor)}
[data-vibeui-block="menubar-008"] [data-part="menu"]{
position-anchor:var(--vibeui-menubar-008-anchor);
position-area:bottom span-right;margin-top:0.375rem;
position-try-fallbacks:flip-block,flip-inline;
}
[data-vibeui-block="menubar-008"] [data-part="menu"][data-align="end"]{position-area:bottom span-left}
}
[data-vibeui-block="menubar-008"] [data-part="item"]{
display:block;width:100%;min-height:1.875rem;padding:0.3125rem 0.5rem;box-sizing:border-box;
appearance:none;border:0;background:none;cursor:pointer;border-radius:0.4375rem;
font:inherit;font-size:0.8125rem;color:inherit;text-align:left;
}
[data-vibeui-block="menubar-008"] [data-part="item"]:hover{background:var(--vibeui-menubar-008-hover)}
[data-vibeui-block="menubar-008"] [data-part="item"]:focus-visible{outline:2px solid var(--vibeui-menubar-008-accent);outline-offset:-2px}
[data-vibeui-block="menubar-008"] [data-part="head"]{
padding:0.4375rem 0.5rem;border-bottom:1px solid var(--vibeui-menubar-008-border);
margin-bottom:0.25rem;font-size:0.75rem;color:var(--vibeui-menubar-008-muted);
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="menubar-008"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_MENUS: Menubar008Menu[] = [
  { label: "Файл", items: ["Новый проект", "Открыть…", "Сохранить"] },
  { label: "Правка", items: ["Отменить", "Повторить", "Найти"] },
  { label: "Вид", items: ["Сетка", "Боковая панель"] },
]

const PROFILE_ITEMS = ["Профиль", "Рабочая область", "Выйти"]

/**
 * Строка меню с правой частью: уведомления и профиль.
 * Один файл, ноль зависимостей, собственная палитра, клиентского JS нет.
 */
export function Menubar008({
  menus = DEFAULT_MENUS,
  userName = "Анна Крылова",
  unread = 4,
  accent,
  className,
  style,
}: Menubar008Props) {
  const palette = {
    ...(accent ? { "--vibeui-menubar-008-accent": accent } : null),
    ...style,
  } as CSSProperties

  const initials = userName
    .split(" ")
    .map((part) => part.charAt(0))
    .join("")
    .slice(0, 2)

  return (
    <>
      <style href="vibeui-menubar-008" precedence="medium">
        {STYLES}
      </style>
      <div
        data-vibeui-block="menubar-008"
        className={className}
        style={palette}
      >
        <div data-part="shell" role="menubar" aria-label="Меню приложения">
          {menus.map((menu, index) => {
            const id = `vibeui-menubar-008-${index}`
            const anchor = {
              "--vibeui-menubar-008-anchor": `--${id}`,
            } as CSSProperties

            return (
              <span key={menu.label} data-part="slot" style={anchor}>
                <button
                  type="button"
                  data-part="trigger"
                  role="menuitem"
                  aria-haspopup="menu"
                  popoverTarget={id}
                >
                  {menu.label}
                </button>
                <div id={id} data-part="menu" popover="auto" role="menu">
                  {menu.items.map((item) => (
                    <button
                      key={item}
                      type="button"
                      data-part="item"
                      role="menuitem"
                    >
                      {item}
                    </button>
                  ))}
                </div>
              </span>
            )
          })}
          <span data-part="right">
            <span
              data-part="slot"
              style={
                {
                  "--vibeui-menubar-008-anchor": "--vibeui-menubar-008-bell",
                } as CSSProperties
              }
            >
              <button
                type="button"
                data-part="trigger"
                role="menuitem"
                aria-haspopup="menu"
                aria-label={`Уведомления, непрочитанных ${unread}`}
                popoverTarget="vibeui-menubar-008-bell-menu"
              >
                <span data-part="bell-glyph" aria-hidden="true">
                  ✉
                </span>
                <span data-part="dot" aria-hidden="true">
                  {unread}
                </span>
              </button>
              <div
                id="vibeui-menubar-008-bell-menu"
                data-part="menu"
                data-align="end"
                popover="auto"
                role="menu"
              >
                <p data-part="head">Непрочитанные</p>
                <button type="button" data-part="item" role="menuitem">
                  Сборка прошла
                </button>
                <button type="button" data-part="item" role="menuitem">
                  Комментарий к странице
                </button>
                <button type="button" data-part="item" role="menuitem">
                  Домен подтверждён
                </button>
              </div>
            </span>
            <span
              data-part="slot"
              style={
                {
                  "--vibeui-menubar-008-anchor": "--vibeui-menubar-008-user",
                } as CSSProperties
              }
            >
              <button
                type="button"
                data-part="trigger"
                role="menuitem"
                aria-haspopup="menu"
                popoverTarget="vibeui-menubar-008-user-menu"
              >
                <span data-part="face" aria-hidden="true">
                  {initials}
                </span>
                <span data-part="who">{userName}</span>
              </button>
              <div
                id="vibeui-menubar-008-user-menu"
                data-part="menu"
                data-align="end"
                popover="auto"
                role="menu"
              >
                <p data-part="head">{userName}</p>
                {PROFILE_ITEMS.map((item) => (
                  <button
                    key={item}
                    type="button"
                    data-part="item"
                    role="menuitem"
                  >
                    {item}
                  </button>
                ))}
              </div>
            </span>
          </span>
        </div>
      </div>
    </>
  )
}