Header blocks
Two blocks build a header rather than a page. They go in createNavigation({ blocks })’s Header items. The field uses NAV_ITEMS_FIELD — a kit editor, not Payload’s stock blocks UI. Footer is a sibling Global from the same call, not a page block.
| Block | Slug | Renders |
|---|---|---|
| Mega menu | megaMenu | MegaMenuPanel |
| Link | navLink | the bar's own link |
The global
Section titled “The global”megaMenuBlock is a factory because the featured-link variants and the icon list are the site’s. The CMS only ever offers approved values, the same rule the sites use for block backgrounds.
import { createNavigation } from "@bison-lab/payload-core";import { LinkBlock, megaMenuBlock } from "@bison-lab/payload-blocks";
globals: [ ...createNavigation({ blocks: [ megaMenuBlock({ variants: [ { label: "Lumbar", value: "lumbar" }, { label: "SI joint", value: "si" }, ], icons: [{ label: "Help", value: "help" }], }), LinkBlock, ], }),];Then payload generate:types, payload generate:importmap, and payload migrate:create. Until the import map includes NavItemsField and AdminWorkspace, those slots render as nothing. bar stays in the schema (hide on scroll, shared viewport, default panel width) and is hidden in the admin — headerFromNavigation still reads it.
createNavigation returns two Globals so each has its own Save / Publish / drafts: Header (navigation / nav) and Footer (navigation-footer / navf). One Features tick (navigation, passed as a createFeatures extra). getNavigation({ payload, draft }) finds both and returns { header, footer, bar }. Footer link-column widths add to 100% of the space beside the logo, not of the full row. Brand copy and social URLs sit on the Logo column.
import { getNavigation } from "@bison-lab/payload-core/navigation";
const { header, footer, bar } = await getNavigation({ payload, draft });The header
Section titled “The header”One call turns the fetched Header global into FloatingNavBlock props. Looks come from chrome roles (megaMenuVariantsFromLooks), not a list on Header:
"use client";import { FloatingNavBlock, megaMenuVariantsFromLooks } from "@bison-lab/ui";import { headerFromNavigation } from "@bison-lab/payload-blocks/react";import Link from "next/link";import { usePathname } from "next/navigation";import type { Navigation } from "@/payload-types";import { NextBlockLink } from "@/blocks/adapters";
export function SiteHeader({ doc }: { doc: Navigation }) { const pathname = usePathname(); return ( <FloatingNavBlock {...headerFromNavigation(doc, { looks: megaMenuVariantsFromLooks(["lumbar", "si"], { lumbar: <Marker region="lumbar" />, si: <Marker region="si" />, }), icons: { help: <CircleHelp className="size-4" aria-hidden /> }, isActive: (href) => pathname.startsWith(href), linkComponent: NextBlockLink, })} renderLink={({ href, children, ...rest }) => ( <Link href={href} {...rest}>{children}</Link> )} /> );}Rows the bar cannot show are dropped rather than rendered broken: a row with no label, a mega menu with nothing to open, and any block type a site added that the package does not know. headerFromNavigation maps only the saved items; it does not invent a Home link. The logo is the home link.
MegaMenuBlockRenderer renders one panel on its own, which is what a live preview of the row wants. It is not a page block and has no place in the registry.