GrNavbar
Top navigation for the main sections and global actions.
Machine-translated from the Russian original, not yet reviewed. Read the original
When to take it
- the application has a top bar — a title, search, a profile, notifications in three zones;
- the bar has to stay visible —
stickywhile scrolling long pages; - a menu button is needed on mobile —
showMenuButtonopens aGrDraweror aGrSidebar; - a
bannerlandmark is needed — the root renders as a<header>tag, and walking by landmarks works.
When to take something else
| Need | Take |
|---|---|
| Side navigation | GrSidebar |
| A bottom bar of sections on mobile | GrBottomNav |
| A bar of actions above a table or a dashboard | GrButtonGroup / GrDashboardToolbar |
| The path to the current page | GrBreadcrumbs |
The three zones
<GrNavbar>
<template #title>
<RouterLink to="/">Granularity</RouterLink>
</template>
<template #center>
<GrInput placeholder="Search" size="sm" aria-label="Search" />
</template>
<GrButton size="sm">Publish</GrButton>
</GrNavbar>
| Slot | Where |
|---|---|
title | on the left, after the menu button; replaces the title string |
left | right after the title — tabs, a section switch |
center | in the middle of the bar — search, breadcrumbs |
| the default one | on the right — actions, the avatar |
The title prop is optional: the title is assembled as markup through the slot. When there is
neither a string nor a slot, the title block is not rendered and does not eat up the padding.
The central zone appears only together with the slot. With it the side zones divide the remainder equally — otherwise the “centre” would be computed from the remainder and would drift after the wider side.
Sticking
<GrNavbar sticky />
sticky keeps the bar at the top while scrolling. The layer is --gr-z-navbar (900): below the
anchored panels (--gr-z-dropdown — 1000), so an open list, a tooltip or a modal covers the bar
rather than moving under it. The details — ../z-index.md.
The height
It is set by the --gr-navbar-height variable (56px by default) — on the bar itself, on an
ancestor or in the theme:
.app-shell { --gr-navbar-height: 48px; }Playground 5
Loading…
<GrNavbar />Install
npm i @feugene/granularityImport
import { GrNavbar } from '@feugene/granularity/components/GrNavbar'API
Props
| Prop | Type | default | Description |
|---|---|---|---|
title | string | undefined | undefined | The heading as a string. The `#title` slot is stronger and makes the prop unnecessary. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | The step of the menu button — and of it alone: everything else in the header belongs to the consumer, and the height of the row is set by `--gr-navbar-height`. The fallback is `sm` (32px) rather than the `md` shared by the package: the button has been that size from the very beginning, and raising the default would shift the header for everyone. If a touch target is needed, `size="lg"` gives 44px, exactly what WCAG 2.5.5 and the Apple HIG require; it fits into a row of 56px. |
showMenuButton | boolean | undefined | false | — |
menuButtonClass | string | undefined | "" | Extra classes applied to the menu button wrapper (e.g. `sm:hidden`). |
sticky | boolean | undefined | false | The bar sticks to the top while scrolling. The layer is `--gr-z-navbar`: it is below the anchored panels, so that an open list does not slide under the header. |
Slots
| Slot | Type | Description |
|---|---|---|
title | any | The heading as a whole — instead of the `title` string. |
left | any | The zone right after the heading: tabs, a section switch. |
center | any | The central zone: search, breadcrumbs. |
default | any | The right zone: actions, the avatar. |
Events
| Event | Type | Description |
|---|---|---|
menu | [] | — |
Examples 3
Actions slot in page shell
Правый слот — для глобальных действий уровня приложения: поиск, уведомления, аккаунт. Для навигации по разделам используйте GrSidebar (десктоп) или GrBottomNav (мобайл), а не сам хедер.
<script setup lang="ts">
import { ref } from 'vue'
import { GrAvatar, GrBadge, GrButton, GrNavbar } from '@feugene/granularity'
const notifications = ref(3)
</script>
<template>
<!--
GrNavbar — это ГЛОБАЛЬНЫЙ верхний хедер приложения (`<header>`-landmark),
который тянется на всю ширину над маршрутизируемым контентом. Здесь он показан
в контексте настоящего app-shell: хедер сверху + контент под ним.
Не путайте с секционными заголовками страниц и marketing-навигацией.
-->
<div class="overflow-hidden rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-bg)] shadow-[var(--gr-shadow-1)]">
<GrNavbar title="Acme Console">
<GrButton size="sm" variant="ghost" square aria-label="Search">
<span class="i-lucide-search block h-4 w-4" aria-hidden="true" />
</GrButton>
<GrButton size="sm" variant="ghost" square aria-label="Notifications" @click="notifications = 0">
<span class="relative inline-flex">
<span class="i-lucide-bell block h-4 w-4" aria-hidden="true" />
<GrBadge v-if="notifications" size="xs" tone="danger" dark class="absolute -right-2 -top-2">
{{ notifications }}
</GrBadge>
</span>
</GrButton>
<GrAvatar :size="28" alt="Ada Lovelace" />
</GrNavbar>
<!-- Условный контент приложения под хедером -->
<div class="grid gap-3 p-5">
<div class="h-3 w-40 rounded bg-[var(--gr-muted)]" />
<div class="grid gap-2 sm:grid-cols-3">
<div class="h-16 rounded-lg border border-[var(--gr-brd)] bg-[var(--gr-card)]" />
<div class="h-16 rounded-lg border border-[var(--gr-brd)] bg-[var(--gr-card)]" />
<div class="h-16 rounded-lg border border-[var(--gr-brd)] bg-[var(--gr-card)]" />
</div>
</div>
</div>
<p class="mt-3 text-sm text-[var(--gr-muted-fg)]">
Правый слот — для глобальных действий уровня приложения: поиск, уведомления, аккаунт.
Для навигации по разделам используйте <code>GrSidebar</code> (десктоп) или
<code>GrBottomNav</code> (мобайл), а не сам хедер.
</p>
</template>Responsive menu trigger
<script setup lang="ts">
import { ref } from 'vue'
import { GrNavbar } from '@feugene/granularity'
const isMenuOpen = ref(false)
const navItems = ['Overview', 'Deployments', 'Billing', 'Settings']
</script>
<template>
<!--
Граница применения: кнопка-гамбургер (`show-menu-button`) нужна ТОЛЬКО в
компактном/мобильном режиме, когда постоянный `GrSidebar` скрыт. Событие `menu`
открывает off-canvas панель навигации. На десктопе кнопку прячут (`sm:hidden`),
а разделы живут в боковой панели.
-->
<div class="relative overflow-hidden rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-bg)] shadow-[var(--gr-shadow-1)]">
<GrNavbar
title="Mobile shell"
show-menu-button
@menu="isMenuOpen = !isMenuOpen"
/>
<div class="relative min-h-[160px]">
<!-- Off-canvas панель навигации, которую открывает кнопка меню -->
<transition
enter-active-class="transition-transform duration-200 ease-out"
enter-from-class="-translate-x-full"
leave-active-class="transition-transform duration-150 ease-in"
leave-to-class="-translate-x-full"
>
<nav
v-if="isMenuOpen"
class="absolute inset-y-0 left-0 z-10 w-52 border-r border-[var(--gr-brd)] bg-[var(--gr-card)] p-3"
>
<button
v-for="item in navItems"
:key="item"
type="button"
class="block w-full rounded-lg px-3 py-2 text-left text-sm text-[var(--gr-fg)] transition-colors hover:bg-[var(--gr-muted)]"
@click="isMenuOpen = false"
>
{{ item }}
</button>
</nav>
</transition>
<div class="p-5 text-sm text-[var(--gr-muted-fg)]">
{{ isMenuOpen ? 'Navigation drawer is open — pick a section.' : 'Tap the hamburger to open the navigation drawer.' }}
</div>
</div>
</div>
</template>Custom title slot
#title (брендинг, статус-бейдж) и #left, по центру #center — поиск или хлебные крошки, справа слот по умолчанию. Проп title не нужен, когда заголовок собран разметкой. С sticky панель остаётся сверху при прокрутке, но не перекрывает выпадающие панели. <script setup lang="ts">
import { GrBadge, GrButton, GrInput, GrNavbar } from '@feugene/granularity'
</script>
<template>
<div class="grid gap-3 rounded-xl border border-[var(--gr-brd)] bg-[var(--gr-bg)] p-3">
<GrNavbar sticky>
<template #title>
<div class="flex items-center gap-2">
<span class="text-sm font-semibold">Release dashboard</span>
<GrBadge size="sm" tone="info">
Beta
</GrBadge>
</div>
</template>
<template #center>
<GrInput placeholder="Search releases" size="sm" aria-label="Search releases" />
</template>
<GrButton size="sm">
Publish
</GrButton>
</GrNavbar>
<div class="px-4 py-3 text-sm text-[var(--gr-muted-fg)]">
Три зоны хедера: слева <code>#title</code> (брендинг, статус-бейдж) и <code>#left</code>,
по центру <code>#center</code> — поиск или хлебные крошки, справа слот по умолчанию.
Проп <code>title</code> не нужен, когда заголовок собран разметкой. С <code>sticky</code>
панель остаётся сверху при прокрутке, но не перекрывает выпадающие панели.
</div>
</div>
</template>