GrNavbar
Top navigation for the main sections and global actions.
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 —
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 |
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrNavbar title="Granularity" />Examples 3 each with a live demo and its code
API generated from the package · cannot drift
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. |
headingLevel | 2 | 1 | 3 | 4 | 5 | 6 | undefined | undefined | Make 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. |
showMenuButton | boolean | undefined | false | A "burger" button to the left of the heading; a press arrives as the `menu` event. `false` by default. |
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 | Signature | Description |
|---|---|---|
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 type { GrNavbarProps, GrNavbarEmits, GrNavbarHeadingLevel } from '@feugene/granularity/components/GrNavbar'Filedist/types/components/GrNavbar/GrNavbar.vue.d.ts
interface GrNavbarProps {
title?: string;
headingLevel?: GrNavbarHeadingLevel;
showMenuButton?: boolean;
size?: GrComponentSize;
menuButtonClass?: string;
sticky?: boolean;
}interface GrNavbarEmits {
(e: 'menu'): void;
}Filedist/types/components/GrNavbar/grNavbarStyles.d.ts
type GrNavbarHeadingLevel = typeof GR_NAVBAR_HEADING_LEVELS[number];Filedist/types/components/GrNavbar/defaults.d.ts
interface GrNavbarConfigurableProps {
size: GrComponentSize;
}Filedist/types/components/GrNavbar/config.d.ts
const grNavbarConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrNavbar">;Filedist/types/components/GrNavbar/safelist.d.ts
const grNavbarSafelist: string[];Usage from the library prose
The title as a real heading — on request
<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
<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.
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
<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; }Props in this example