Navigation
Column Navmenu
Навигация сайта с выпадающей панелью из колонок: под каждой ссылкой строка пояснения, поэтому по разделу понятно, что внутри.
- navmenu
- mega menu
- popover
- site nav
Превью
Использовать с AI
- 1. Скопируйте ссылку.
- 2. Напишите агенту своими словами и вставьте её в предложение.
- 3. Агент откроет ссылку и поставит компонент из registry.
размести это в шапке: https://vibeui.ru/c/navmenu-001
Показать полную инструкцию
То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.
# Установка и размещение «navmenu-001» (Column Navmenu) из VibeUI
## 1. Сначала установка — не пропускай, не пересоздавай
Выполни эту команду до того, как писать код:
npx shadcn@latest add https://vibeui.ru/r/navmenu-001.json
Registry item: https://vibeui.ru/r/navmenu-001.json
Ставится в: components/vibeui/navmenu-001.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.
Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
компонентом из другой библиотеки и не переписывай под стиль проекта.
## 2. Что это
Навигация сайта с выпадающей панелью из колонок: под каждой ссылкой строка пояснения, поэтому по разделу понятно, что внутри.
Навигация сайта с широкой выпадающей панелью: колонки ссылок, у каждой ссылки строка пояснения. Панель открывает HTML popover и якорит к полосе навигации, поэтому Escape и клик мимо достаются от браузера. Ноль зависимостей, один файл, клиентского JS нет.
## 3. Как использовать
import { Navmenu001 } from "@/components/vibeui/navmenu-001"
<Navmenu001
entries={[
{
label: "Продукт",
columns: [
{ title: "Инструменты", links: [{ label: "Редактор", hint: "Блоки и сетка" }] },
],
},
{ label: "Цены", href: "/pricing" },
]}
/>
Полный список пропсов — в установленном файле.
## 4. Куда поставить
Это мелкий inline-компонент. Поставь его ровно туда, куда просил
пользователь, внутрь существующей разметки. Не создавай ради него
новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
контрол — замени его, а не добавляй второй.
Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)
## 5. Сохранить как установлено
- локальную палитру --vibeui-navmenu-001-* и не подменять её токенами вашей темы (bg-popover, text-muted-foreground и подобными)
- пояснение под каждой ссылкой: имя раздела в мега-меню почти никогда не объясняет, что там внутри
- якорь на всей полосе, а не на отдельной кнопке: широкая панель, привязанная к кнопке, съезжает за край окна
- popover="auto": Escape, клик мимо и закрытие соседней панели достаются от браузера
- auto-fit с minmax в колонках — при узкой панели они перестраиваются сами, без второго набора правил
- разделы без панели обычными ссылками: кнопка, которая просто ведёт на страницу, ломает открытие в новой вкладке
## 6. Можно менять
- массив entries: разделы, их колонки, ссылки и пояснения
- ширину панели и минимальную ширину колонки
- акцент через проп accent — цвет колец фокуса
- высоту полосы навигации
## 7. Правила
- Панель не открывается по наведению — только по клику: случайное раскрытие мега-меню при проезде курсором раздражает сильнее, чем лишний клик.
- Без поддержки anchor positioning панель открывается по центру экрана: проверьте целевые браузеры.
- Больше трёх колонок в панели не ставьте: список перестаёт читаться и превращается в стену ссылок.
- Не выносите CSS-переменные в globals.css: компонент обязан оставаться одним файлом.
## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.Для разработчика
npx shadcn@latest add https://vibeui.ru/r/navmenu-001.jsonhttps://vibeui.ru/r/navmenu-001.jsonКомпонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-navmenu-001-*. Серверный: хуков нет, панель открывает popover="auto". Якорем служит вся полоса навигации (anchor-name на [data-part="bar"]), поэтому широкая панель встаёт под полосой целиком, а не под отдельной кнопкой. Колонки раскладывает grid с auto-fit и minmax, так что их число подстраивается под ширину панели. Разделы без панели — обычные ссылки.
Исходник компонента
Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.
import type { CSSProperties } from "react"
export type Navmenu001Link = {
label: string
hint?: string
href?: string
}
export type Navmenu001Column = {
title: string
links: Navmenu001Link[]
}
export type Navmenu001Entry = {
label: string
href?: string
columns?: Navmenu001Column[]
}
export type Navmenu001Props = {
entries?: Navmenu001Entry[]
accent?: string
className?: string
style?: CSSProperties
}
// Идея компонента: навигация сайта, где выпадающая панель — не список ссылок, а
// колонки с пояснениями: по подписи «Аналитика» непонятно, что внутри, по строке
// под ней — понятно. Панель открывает HTML popover, а якорем служит вся полоса
// навигации, поэтому панель встаёт под ней целиком, а не под отдельной кнопкой.
const STYLES = `
:where([data-vibeui-block="navmenu-001"]){
--vibeui-navmenu-001-bg:oklch(1 0 0);
--vibeui-navmenu-001-fg:oklch(0.22 0.014 265);
--vibeui-navmenu-001-muted:oklch(0.55 0.014 265);
--vibeui-navmenu-001-border:oklch(0.91 0.006 265);
--vibeui-navmenu-001-hover:oklch(0.55 0.02 265 / 8%);
--vibeui-navmenu-001-accent:oklch(0.55 0.2 262);
--vibeui-navmenu-001-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="navmenu-001"]{
box-sizing:border-box;width:100%;max-width:44rem;
font-family:var(--vibeui-navmenu-001-font);color:var(--vibeui-navmenu-001-fg);
}
[data-vibeui-block="navmenu-001"] [data-part="bar"]{
box-sizing:border-box;padding:0.375rem;
display:flex;align-items:center;gap:0.125rem;
background:var(--vibeui-navmenu-001-bg);
border:1px solid var(--vibeui-navmenu-001-border);border-radius:0.75rem;
anchor-name:--vibeui-navmenu-001-bar;
}
[data-vibeui-block="navmenu-001"] [data-part="trigger"],
[data-vibeui-block="navmenu-001"] [data-part="plain"]{
appearance:none;border:0;background:none;cursor:pointer;text-decoration:none;
display:inline-flex;align-items:center;gap:0.375rem;
height:2rem;padding:0 0.75rem;border-radius:0.5rem;
font:inherit;font-size:0.875rem;color:inherit;
transition:background-color .14s ease;
}
[data-vibeui-block="navmenu-001"] [data-part="trigger"]:hover,
[data-vibeui-block="navmenu-001"] [data-part="plain"]:hover{background:var(--vibeui-navmenu-001-hover)}
[data-vibeui-block="navmenu-001"] [data-part="trigger"]:focus-visible,
[data-vibeui-block="navmenu-001"] [data-part="plain"]:focus-visible{outline:2px solid var(--vibeui-navmenu-001-accent);outline-offset:-2px}
[data-vibeui-block="navmenu-001"] [data-part="caret"]{
width:0.375rem;height:0.375rem;margin-top:-0.1875rem;
border:1.5px solid var(--vibeui-navmenu-001-muted);border-left:0;border-top:0;
transform:rotate(45deg);transition:transform .16s ease;
}
[data-vibeui-block="navmenu-001"] [data-part="panel"]{
position:fixed;inset:auto;margin:0;
width:min(40rem,92vw);padding:1rem;box-sizing:border-box;
background:var(--vibeui-navmenu-001-bg);color:var(--vibeui-navmenu-001-fg);
border:1px solid var(--vibeui-navmenu-001-border);border-radius:0.875rem;
font-family:var(--vibeui-navmenu-001-font);
box-shadow:0 24px 48px -24px oklch(0.2 0.03 265 / 40%);
}
/* Якорь — вся полоса: панель широкая и не должна прыгать за отдельной кнопкой. */
@supports (anchor-name: --a){
[data-vibeui-block="navmenu-001"] [data-part="panel"]{
position-anchor:--vibeui-navmenu-001-bar;
position-area:bottom span-right;margin-top:0.5rem;
position-try-fallbacks:flip-block;
}
}
[data-vibeui-block="navmenu-001"] [data-part="columns"]{
display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:1rem 1.5rem;
}
[data-vibeui-block="navmenu-001"] [data-part="title"]{
margin:0 0 0.5rem;font-size:0.6875rem;letter-spacing:0.06em;text-transform:uppercase;
color:var(--vibeui-navmenu-001-muted);
}
[data-vibeui-block="navmenu-001"] [data-part="list"]{margin:0;padding:0;list-style:none;display:grid;gap:0.125rem}
[data-vibeui-block="navmenu-001"] [data-part="link"]{
display:block;padding:0.4375rem 0.5rem;border-radius:0.5rem;text-decoration:none;color:inherit;
}
[data-vibeui-block="navmenu-001"] [data-part="link"]:hover{background:var(--vibeui-navmenu-001-hover)}
[data-vibeui-block="navmenu-001"] [data-part="link"]:focus-visible{outline:2px solid var(--vibeui-navmenu-001-accent);outline-offset:-2px}
[data-vibeui-block="navmenu-001"] [data-part="name"]{display:block;font-size:0.875rem;font-weight:550}
[data-vibeui-block="navmenu-001"] [data-part="hint"]{display:block;margin-top:0.125rem;font-size:0.75rem;line-height:1.45;color:var(--vibeui-navmenu-001-muted)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="navmenu-001"] *{animation:none!important;transition:none!important}}
`
const DEFAULT_ENTRIES: Navmenu001Entry[] = [
{
label: "Продукт",
columns: [
{
title: "Инструменты",
links: [
{ label: "Редактор", hint: "Собирайте страницы из готовых блоков" },
{ label: "Аналитика", hint: "Просмотры, источники и воронки" },
{ label: "Формы", hint: "Заявки падают в почту и в CRM" },
],
},
{
title: "Платформа",
links: [
{ label: "Домены", hint: "Свой адрес и сертификат за минуту" },
{ label: "Хранилище", hint: "Картинки и файлы рядом с сайтом" },
{ label: "Роли", hint: "Кто редактирует, а кто только смотрит" },
],
},
],
},
{
label: "Решения",
columns: [
{
title: "Для кого",
links: [
{ label: "Студиям", hint: "Десятки сайтов в одной панели" },
{ label: "Магазинам", hint: "Каталог, оплата и доставка" },
{ label: "Медиа", hint: "Поток материалов и авторы" },
],
},
],
},
{ label: "Цены", href: "#" },
{ label: "Документация", href: "#" },
]
/**
* Навигация сайта с выпадающей панелью из колонок ссылок с описаниями.
* Один файл, ноль зависимостей, собственная палитра, клиентского JS нет.
*/
export function Navmenu001({
entries = DEFAULT_ENTRIES,
accent,
className,
style,
}: Navmenu001Props) {
const palette = {
...(accent ? { "--vibeui-navmenu-001-accent": accent } : null),
...style,
} as CSSProperties
return (
<>
<style href="vibeui-navmenu-001" precedence="medium">
{STYLES}
</style>
<nav
data-vibeui-block="navmenu-001"
aria-label="Основная навигация"
className={className}
style={palette}
>
<div data-part="bar">
{entries.map((entry, index) => {
if (!entry.columns) {
return (
<a key={entry.label} data-part="plain" href={entry.href ?? "#"}>
{entry.label}
</a>
)
}
const id = `vibeui-navmenu-001-${index}`
return (
<span key={entry.label}>
<button
type="button"
data-part="trigger"
aria-haspopup="true"
popoverTarget={id}
>
{entry.label}
<span data-part="caret" aria-hidden="true" />
</button>
<div
id={id}
data-part="panel"
popover="auto"
aria-label={entry.label}
>
<div data-part="columns">
{entry.columns.map((column) => (
<div key={column.title}>
<p data-part="title">{column.title}</p>
<ul data-part="list">
{column.links.map((link) => (
<li key={link.label}>
<a data-part="link" href={link.href ?? "#"}>
<span data-part="name">{link.label}</span>
{link.hint ? (
<span data-part="hint">{link.hint}</span>
) : null}
</a>
</li>
))}
</ul>
</div>
))}
</div>
</div>
</span>
)
})}
</div>
</nav>
</>
)
}