GrNavbar

  • core
  • navigation
  • API · 6 props

Top navigation for the main sections and global actions.

import { GrNavbar } from '@feugene/granularity/components/GrNavbar'

Overview

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 — sticky while scrolling long pages;
  • a menu button is needed on mobile — showMenuButton opens a GrDrawer or a GrSidebar;
  • a banner landmark is needed — the root renders as a <header> tag, and walking by landmarks works.

When to take something else

NeedTake
Side navigationGrSidebar
A bottom bar of sections on mobileGrBottomNav
A bar of actions above a table or a dashboardGrButtonGroup / GrDashboardToolbar
The path to the current pageGrBreadcrumbs

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 3 each with a live demo and its code

  • The header of an admin app

    The root is a <header> landmark, so the shell is reachable by structure. The title slot holds the product mark, left the sections right after it, and the default slot the global actions pinned to the right edge — search with its shortcut, notifications with a counter, the account.

    3 unread notificationsMO
    InvoicesThe page under the header changes; the header stays the same on every page.
  • A phone header with the app's own menu

    On a phone the sections hide behind the menu button: show-menu-button draws it with a localised name and emits menu. What opens is the application’s own drawer — a header that owned one would fight every shell that already has its own.

    Orders
    MO
    #5523 · Harbour Road, cabinet B2
    #5524 · Grafton St, boiler service
    #5527 · Dock Mill, alarm test
  • A sticky header over a document

    sticky keeps the header at the top while the document scrolls under it; its layer sits below anchored panels, so an open menu is not hidden. The center zone takes the path and is the only one that stretches, and heading-level makes the document title a real h1.

    Cleaning agreementSaved 2 min ago

    GW

    1. This agreement sets out how Northwind Traders supplies cleaning services to Contoso Ltd at its Dublin office.

    2. Services are provided on weekdays between 18:00 and 22:00. Additional visits are booked through the client portal at least two working days ahead.

    3. Invoices are issued on the first working day of each month and are payable within thirty days. Late payments accrue interest at 1.5% a month.

    4. Either party may end the agreement with sixty days written notice. Equipment provided by the supplier remains its property.

    5. The agreement is governed by the laws of Ireland. Disputes go to mediation before any court proceedings.

API generated from the package · cannot drift

Props

PropTypedefaultDescription
titlestring | undefinedundefinedThe heading as a string. The `#title` slot is stronger and makes the prop unnecessary.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe 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.
headingLevel2 | 1 | 3 | 4 | 5 | 6 | undefinedundefinedMake the heading a real `h1`…`h6`. By default the heading is a `div`, and that is right for most headers: a logo and the name of the application are not headings, and an `h1` on them would distort the structure of the page. But the header of an admin section or the name of a document is a heading indeed, and there is no other way to walk such a page by its structure. Deliberately not configurable through `GrConfigProvider`: "there is no heading" cannot be expressed there (`useGrComponentProp` requires a non-nullable fallback), which means some level would become the shared default. Besides, a shell has one header — a global default would configure a single call site.
showMenuButtonboolean | undefinedfalseA "burger" button to the left of the heading; a press arrives as the `menu` event. `false` by default.
menuButtonClassstring | undefined""Extra classes applied to the menu button wrapper (e.g. `sm:hidden`).
stickyboolean | undefinedfalseThe 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

SlotTypeDescription
titleanyThe heading as a whole — instead of the `title` string.
leftanyThe zone right after the heading: tabs, a section switch.
centeranyThe central zone: search, breadcrumbs.
defaultanyThe right zone: actions, the avatar.

Events

EventSignatureDescription
menu(event: "menu"): void—

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrNavbarProps, GrNavbarEmits, GrNavbarHeadingLevel } from '@feugene/granularity/components/GrNavbar'

Filedist/types/components/GrNavbar/GrNavbar.vue.d.ts

GrNavbarPropsinterface
interface GrNavbarProps {
    title?: string;
    headingLevel?: GrNavbarHeadingLevel;
    showMenuButton?: boolean;
    size?: GrComponentSize;
    menuButtonClass?: string;
    sticky?: boolean;
}
GrNavbarEmitsinterface
interface GrNavbarEmits {
    (e: 'menu'): void;
}

Filedist/types/components/GrNavbar/grNavbarStyles.d.ts

GrNavbarHeadingLeveltype
type GrNavbarHeadingLevel = typeof GR_NAVBAR_HEADING_LEVELS[number];

Filedist/types/components/GrNavbar/defaults.d.ts

GrNavbarConfigurablePropsinterface
interface GrNavbarConfigurableProps {
    size: GrComponentSize;
}

Filedist/types/components/GrNavbar/config.d.ts

grNavbarConfigconst
const grNavbarConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrNavbar">;

Filedist/types/components/GrNavbar/safelist.d.ts

grNavbarSafelistconst
const grNavbarSafelist: string[];

Usage from the library prose

The title as a real heading — on request
vue
<GrNavbar title="Contracts" :heading-level="1" />

By default the title of the bar is a div, and that is not an omission: the logo and the name of the application are not headings, and an h1 on the brand would distort the structure of the page. Most bars are exactly like that.

But the bar of a section of an admin panel or the name of an open document is a heading, and without headingLevel there is nothing to walk such a page by structure with: after the h1 not a single heading turns up in it.

One is allowed, unlike in GrCard, where the scale starts at two. A card is never the heading of a page, while the bar of a section is.

The level is deliberately not configured through GrConfigProvider. “There is no heading” cannot be expressed there, that is, the shared default would become some level — and the brand bars of every consumer would silently get a heading. Besides, a shell has one bar: a global default would configure the single call site.

The look does not depend on the level: the type size and the weight are the same, and the browser’s margin of a heading is cancelled explicitly. The preflight of the package resets margin only on body, so an application with a tailwind-compatible reset would not have seen it — but the height of the bar must not depend on whether the consumer has connected someone else’s reset.

The three zones
vue
<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>
SlotWhere
titleon the left, after the menu button; replaces the title string
leftright after the title — tabs, a section switch
centerin the middle of the bar — search, breadcrumbs
the default oneon 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.

When there is not enough room

The actions on the right are the global interface: search, notifications, the avatar. This zone never shrinks. It is left and center that give way: they are no wider than the remainder and scroll sideways without a visible bar. Scrolling rather than clipping — a link past the edge stays reachable, and keyboard focus scrolls the zone to it. Previously an overflowing left was drawn over the actions: at 600px “Reports” lay on top of “Search”. Actions wider than their share with a central zone present do not shrink either: in that case the centre shifts, but nothing overlaps.

A header with many sections on a narrow screen is a reason to move them into the menu (showMenuButton) rather than to rely on scrolling.

Sticking
vue
<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:

css
.app-shell { --gr-navbar-height: 48px; }
The menu button

showMenuButton shows the button and emits menu; its accessible name comes from the locale (gr.navbar.openMenu). menuButtonClass is needed to hide the button on wide screens (sm:hidden) — the bar does not decide on the application’s behalf when the menu folds away.

size sets the step of that button only — everything else in the header belongs to the consumer, and the height of the row is held by --gr-navbar-height. It is read from GrConfigProvider (componentDefaults.GrNavbar.size).

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 proper touch target is needed, size="lg" gives 44px and fits into a row 56px tall.