Skip to content

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.

BlockSlugRenders
Mega menumegaMenuMegaMenuPanel
LinknavLinkthe bar's own link

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, the items field renders as nothing. Header Settings (bar) is a schema change too — a site without that migrate never sees hide on scroll, shared viewport, or the default panel width.

One call turns the fetched Header global into FloatingNavBlock props. Looks come from chrome roles (megaMenuVariantsFromLooks), not a list on Header:

components/SiteHeader.tsx
"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.

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.