Doc Frame
The themed doc-page shell that wraps DocNav — a sticky header with a flexible brand/logo slot, the nav slot, and a content wrapper for the doc. Copy it into your project and own it completely.
Basic
When to use
Use DocFrame for the surrounding themed page shell of a documentation site: a sticky header with a flexible brand/logo slot, the DocNav slot, and a content wrapper for the doc. It reads a single docs manifest, derives the active product, and passes the docs slice plus active-state down to DocNav. Everything is themed by Visor tokens, so the shell adopts the active project theme without modification.
DocFrame owns the page; DocNav owns the nav. This is distinct from Navbar (top-level site navigation with brand and actions) — reach for DocFrame when you are rendering a documentation shell from a manifest.
Anatomy
DocFrame derives everything from the manifest:
- Sticky header — the brand/logo slot on the left, then the
DocNavslot. It sticks to the page scroll so brand and nav never leave view. - Content wrapper — the doc, passed as
children, rendered below the nav. - Product accordion — when the manifest has a
productsroster (or docs with distinctscopevalues),DocFrameseeds the open product fromcurrentPath(or theactiveProductprop) and drives theDocNavaccordion. With no roster it degrades to a single grouped row.
Header chrome
The sticky header carries, left to right:
- Brand slot — the resolved logo (see below). The text fallback leads with a glyph chip in the theme's
--primarybrand color. - Home pill (
home) — anOVERVIEWchip with a leading compass glyph, mono UPPERCASE, that links back to the docs hub. Pass{ href, label }. - Meta slot (
meta) — a right-aligned breadcrumb / status line, mono UPPERCASE and--text-tertiary— e.g.ARTIST · BUILD-READY.
<DocFrame
manifest={manifest}
home={{ href: '/docs', label: 'Overview' }}
meta="Artist · Build-Ready"
>
{children}
</DocFrame>Group color-coding
DocFrame drives DocNav's --doc-nav-group-accent hook so each group's dot reads a distinct hue: the pinned Shared group is --info (blue), product groups are --accent, the Pro group is --warning (amber), and the Appendix is muted. Override any group with groupAccents (keyed by group id or role):
<DocFrame manifest={manifest} groupAccents={{ pro: 'var(--warning)', beta: 'var(--accent)' }}>
{children}
</DocFrame>Borderless themes (Animal, ENTR) carry structure from surface contrast, not borders. DocFrame auto-nulls the pinned group's frame for the known borderless set; pass borderless explicitly when the theme is applied by an ancestor.
Logo resolution
The brand slot resolves in order:
- The
logoprop — any node: an<img>of an SVG, an inline<svg>, or a full component (an animated mark, a Visor<Brand>). - The active theme's brand logo — the mode-aware
--brand-logocustom property, shown when the theme ships one. The resting state is always the text wordmark, upgraded client-side, so there is never a flash of invisibility on reload. - The manifest
brandtext — a plain wordmark, mode-adaptive via--text-primary.
// Explicit logo — an SVG file, an inline SVG, or any component.
<DocFrame manifest={manifest} logo={<img src="/logo.svg" alt="Blacklight" />}>
{children}
</DocFrame>Installation
npx visor add doc-frameThis also pulls in the doc-nav component, which DocFrame renders in its nav slot.
Usage
import { DocFrame, type DocsManifest } from '@/components/ui/doc-frame/doc-frame';
const manifest: DocsManifest = {
brand: 'Blacklight',
products: [{ id: 'artist' }, { id: 'pro' }],
docs: [
{ order: 1, label: 'Charter', href: '/docs/charter.html', group: 'Shared' },
{ order: 3, label: 'Screens', href: '/docs/artist/screens', scope: ['artist'], group: 'Artist' },
{ order: 2, label: 'Journeys', href: '/docs/pro/journeys', scope: ['pro'], group: 'Pro' },
{ order: 11, label: 'Q3 Audit', href: '/docs/q3-audit.html' },
],
};
export function Shell({ children }: { children: React.ReactNode }) {
return (
<DocFrame manifest={manifest} currentPath="/docs/artist/screens">
{children}
</DocFrame>
);
}For a Next route, pass currentPath={usePathname()} so active-state resolves during SSR; a static page passes location.pathname.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
manifest* | DocsManifest | — | The parsed manifest (docs[], optional products[], optional brand text, optional dispositions) — the single input everything derives from. |
children* | React.ReactNode | — | The doc content, rendered in the content wrapper below the nav. |
logo | React.ReactNode | — | The brand slot. Any node — an <img> of an SVG, an inline <svg>, or a full component. Resolution order: explicit logo → the active theme's brand logo SVG (mode-aware via --brand-logo) → the manifest brand text. |
activeProduct | string | — | Which product group is expanded (the accordion). Defaults to the route's product, else the first product in the roster. Absent roster → single-product mode. |
currentPath | string | — | The active route for active-state. Next consumers pass usePathname(); a static page passes location.pathname. Defaults to window.location.pathname in the browser, or "/" during SSR. |
theme | string | — | A Visor theme class name applied to the shell root, scoping all doc-shell CSS variables. Defaults to the app's ambient theme. |
Command Palette
A searchable command palette for quick actions. Built on cmdk — copy it into your project and own it completely.
Doc Nav
A manifest-driven, grouped/collapsible, multi-product-aware documentation navigation — a pinned Shared set beside accordion product groups and an Appendix for ad-hoc docs. Copy it into your project and own it completely.