Section Bar
The page's one sticky bar on deep content: where you are, and a jump to any section. It swaps with the header and the two never stack.
import { SectionBar } from '@fairgarden-private/design/components/SectionBar'
A Base UI Toolbar in a nav named "In this section", on the page ground of the band it covers, with a hairline bottom rule and no shadow. It stays one line, at least 44 px tall. Pass items (the ancestors, root first), current (the page) and sections (each section's element id and label, in order). The bar follows the section in view by itself; pass activeSection to control it.
Docking. Pass onDockChange and feed its value back into docked. The bar docks when a sentinel at its in-flow position leaves the top of the viewport. On upward scroll the page shell brings the header back in its place, and on downward scroll the bar returns. Give the page's sections a scroll-margin-top of the bar's height so a jump lands clear of it.
Section bar
Section bar
Below 768 px the disclosure glyph opens the staircase breadcrumb, the caps title follows the section in view and "Jump to" opens the list. From 768 px the inline breadcrumb (the Breadcrumb's staircase kind, its crumbs part of the toolbar) takes their place: it collapses the middle into a "…" menu past four levels (six from 1024 px), truncates long crumbs and sets the current crumb in bold as the title. The Jump label names the section in view, announced politely once per change. The Jump panel is a 320 px list of links: Tab moves through them and Escape closes it. Following one closes the panel and moves focus to that section's heading. capped adds the green top rule.
What community-centered conservation is
Land trusts work with the people who live on and near the land. This section runs long enough to scroll, so the bar can dock and follow the section in view.
For whom? By whom?
Land trusts work with the people who live on and near the land. This section runs long enough to scroll, so the bar can dock and follow the section in view.
How land trusts begin
Land trusts work with the people who live on and near the land. This section runs long enough to scroll, so the bar can dock and follow the section in view.
'use client'
import * as React from 'react'
import { SectionBar } from '@fairgarden-private/design/components/SectionBar'
import styles from './section.module.css'
const sections = [
{ id: 'bar-what', label: 'What community-centered conservation is' },
{ id: 'bar-who', label: 'For whom? By whom?' },
{ id: 'bar-how', label: 'How land trusts begin' },
]
/** The section bar over three sections; it docks inside this frame as you scroll. */
export function SectionBarSection() {
const [docked, setDocked] = React.useState(false)
return (
<div className={styles.page}>
<SectionBar
capped
docked={docked}
onDockChange={setDocked}
items={[
{ label: 'Home', href: '#bar-what' },
{ label: 'Resources', href: '#bar-what' },
{ label: 'Guides', href: '#bar-what' },
]}
current="Community-centered conservation"
sections={sections}
/>
{sections.map((section) => (
<section key={section.id} id={section.id} className={styles.section}>
<h2 className={styles.heading}>{section.label}</h2>
<p className={styles.text}>
Land trusts work with the people who live on and near the land. This section runs long
enough to scroll, so the bar can dock and follow the section in view.
</p>
</section>
))}
</div>
)
}
Tab strip
Tab strip
With kind="tabs" and 6 or fewer sections, from 1024 px equal cells of at least 160 px replace the breadcrumb and Jump to. The cell in view carries aria-current="location", a 3 px bar and bold weight. A click scrolls to the section and writes its hash. toTop adds the square "Back to top" cell at the start, part of the docked bar, so nothing floats; it moves focus to the skip link's target. With more than 6 sections the bar stays a section bar.
Land Trust Alliance
Scroll to move the current cell; click a cell to jump.
Community
Scroll to move the current cell; click a cell to jump.
Affiliates
Scroll to move the current cell; click a cell to jump.
'use client'
import * as React from 'react'
import { SectionBar } from '@fairgarden-private/design/components/SectionBar'
import styles from './tabs.module.css'
const sections = [
{ id: 'tabs-alliance', label: 'Land Trust Alliance' },
{ id: 'tabs-community', label: 'Community' },
{ id: 'tabs-affiliates', label: 'Affiliates' },
]
/** The tab strip from 1024 px, with the "Back to top" cell; below 1024 px it is a section bar. */
export function SectionBarTabs() {
const [docked, setDocked] = React.useState(false)
return (
<div className={styles.page} id="tabs-top" tabIndex={-1}>
<SectionBar
kind="tabs"
toTop="#tabs-top"
docked={docked}
onDockChange={setDocked}
preset="white"
items={[{ label: 'Home', href: '#tabs-top' }]}
current="Connect"
sections={sections}
/>
{sections.map((section) => (
<section key={section.id} id={section.id} className={styles.section}>
<h2 className={styles.heading}>{section.label}</h2>
<p className={styles.text}>Scroll to move the current cell; click a cell to jump.</p>
</section>
))}
</div>
)
}
Guide bar
Guide bar
kind="guide" takes an optional search at the start (an Autocomplete, capped at 240 px, from 768 px), centres the breadcrumb, and ends on an optional listen jump. That is a standalone link with a count badge, and it moves focus to the recordings heading.
Overview
A plump thrush of eastern forests, rust above and spotted below.
Songs and calls
Following Listen moves focus to this heading.
'use client'
import * as React from 'react'
import { SectionBar } from '@fairgarden-private/design/components/SectionBar'
import { Search } from '@fairgarden-private/design/components/Search'
import styles from './guide.module.css'
const birds = [
{ value: 'Wood Thrush', secondaryName: 'Hylocichla mustelina' },
{ value: 'Hermit Thrush', secondaryName: 'Catharus guttatus' },
{ value: 'Veery', secondaryName: 'Catharus fuscescens' },
]
/** The guide bar: search at the start, the centred breadcrumb, the Listen jump at the end. */
export function SectionBarGuide() {
const [docked, setDocked] = React.useState(false)
return (
<div className={styles.page}>
<SectionBar
kind="guide"
docked={docked}
onDockChange={setDocked}
search={<Search label="Search the guide" hideSubmit items={birds} />}
items={[
{ label: 'Bird guide', href: '#guide-overview' },
{ label: 'Thrushes', href: '#guide-overview' },
]}
current="Wood Thrush"
listen={{ href: '#guide-recordings', count: 6 }}
sections={[
{ id: 'guide-overview', label: 'Overview' },
{ id: 'guide-recordings', label: 'Songs and calls' },
]}
/>
<section id="guide-overview" className={styles.section}>
<h2 className={styles.heading}>Overview</h2>
<p className={styles.text}>A plump thrush of eastern forests, rust above and spotted below.</p>
</section>
<section id="guide-recordings" className={styles.section}>
<h2 className={styles.heading}>Songs and calls</h2>
<p className={styles.text}>Following Listen moves focus to this heading.</p>
</section>
</div>
)
}
API Reference
The page’s sticky section bar: a Base UI Toolbar in a nav named “In
this section”, on the covered band’s page ground, with a
--border-size-1 --role-rule bottom rule and no shadow; one line, at
least --ds-size-hit tall.
- Base: the disclosure glyph opens the staircase breadcrumb (capped at half the viewport), the caps title follows the section in view, and “Jump to” opens the list of sections.
- From
--md-n-above: the inline breadcrumb (root → current, the current crumb at--font-weight-7) replaces glyph and title, and the Jump label carries the section in view, announced politely once per change. - The Jump panel is a Popover of links in a
nav“On this page”,--size-px-14wide: Tab moves through the links, Escape closes it, and following a link closes it and moves focus to the section’s heading. kind="tabs": from--lg-n-above, with 6 or fewer sections, equal cells of at least--size-px-12replace Jump to, with scrollspy (aria-current="location") and an optionaltoTopcell at the start.kind="guide": an optional startsearchand an endlistenjump.
Docking: pass onDockChange and feed docked; bring the header back in
its place on upward scroll. Hidden in print; the path prints in the
masthead.
| Prop | Type | Description |
|---|---|---|
label | | The landmark’s name. Default “In this section”. |
activeSection | | The section in view, controlled. Omitted, the bar follows scroll (scrollspy). |
capped | | The optional top rule: |
current | | The current page: the last crumb, at |
docked | | The docked position: sticky at the top edge on |
items | | The ancestors, root first: the inline breadcrumb from |
jumpLabel | | The Jump trigger’s word. Default “Jump to”. |
kind | |
|
listen | | |
onActiveSectionChange | | Called when the section in view changes. |
onDockChange | | Called when the sentinel at the bar’s in-flow position leaves ( |
preset | | The page ground of the band the bar covers. Default: the enclosing page ground, else |
primary | | Primary Radix scale for the bar’s scope. Never defaulted [D133]. |
renderLink | | Builds each crumb anchor, e.g. |
search | | |
secondary | | Secondary Radix scale: the Jump indicator, tab hover and link hover. Never defaulted. |
sections | | The page’s sections, in order: the Jump list, the base title and the tab strip. |
share | | Share links (inline-tier icon links), from |
toTop | | |
toggleLabel | | The staircase toggle’s accessible name. Default “Page path”. |
tools | | End tools, such as the §9.4 audio Toggle at |
sectionBar
type sectionBar = sectionBarSectionBarKind
The three §11.7 builds.
type SectionBarKind = 'section' | 'guide' | 'tabs'<'section' | 'guide' | 'tabs' | null | undefined>SectionBarListen
The guide bar’s listen jump [D184]: a standalone link with an opaque count badge.
type SectionBarListen = {
/** The recordings' anchor, e.g. "#recordings"; following it moves focus to that heading. */
href: string;
/** Default "Listen". */
label?: React.ReactNode;
/** The number of recordings, shown in a count Badge. */
count?: number;
}SectionBarProps
Props for SectionBar: nav props, the kind with its parts, the path, the sections and the color axes.
type SectionBarProps = (
| { kind?: 'section'; search?: undefined; listen?: undefined; toTop?: undefined }
| { kind: 'guide'; search?: React.ReactNode; listen?: SectionBarListen; toTop?: undefined }
| { kind: 'tabs'; toTop?: string; search?: undefined; listen?: undefined }
) & {
/** The optional top rule: `--border-size-2` in the brand green, decorative like the strip. Default `false`. */
capped?: boolean;
/**
* The docked position: sticky at the top edge on `--layer-2`. The page
* shell sets it from `onDockChange` and swaps it with the header on
* upward scroll; the two never stack [D89, D183]. Default `false`.
*/
docked?: boolean;
/** Primary Radix scale for the bar's scope. Never defaulted [D133]. */
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/** Secondary Radix scale: the Jump indicator, tab hover and link hover. Never defaulted. */
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
/** The page ground of the band the bar covers. Default: the enclosing page ground, else `paper`. */
preset?: 'paper' | 'white' | 'tide' | 'meadow' | 'pollen' | 'apricot' | 'rose' | 'heather';
/** The landmark's name. Default "In this section". */
label?: string;
/** The ancestors, root first: the inline breadcrumb from `--md-n-above`, the staircase below it. */
items?: BreadcrumbCrumb[];
/** The current page: the last crumb, at `--font-weight-7`, acting as the title [D184]. */
current: React.ReactNode;
/** The page's sections, in order: the Jump list, the base title and the tab strip. */
sections?: SectionBarSection[];
/** The section in view, controlled. Omitted, the bar follows scroll (scrollspy). */
activeSection?: string;
/** Called when the section in view changes. */
onActiveSectionChange?: (id: string) => void;
/**
* Called when the sentinel at the bar's in-flow position leaves (`true`)
* or re-enters (`false`) the viewport top [D183]. Pass it to wire `docked`.
*/
onDockChange?: (docked: boolean) => void;
/** The Jump trigger's word. Default "Jump to". */
jumpLabel?: string;
/** The staircase toggle's accessible name. Default "Page path". */
toggleLabel?: string;
/** End tools, such as the §9.4 audio Toggle at `--size-px-7`. Below 360 px they move into the staircase panel. */
tools?: React.ReactNode;
/** Share links (inline-tier icon links), from `--xl-n-above` [D184]. */
share?: React.ReactNode;
/** Builds each crumb anchor, e.g. `(href) => <NextLink href={href} />`. */
renderLink?: (href: string) => ReactElement;
}SectionBarSection
One in-page section: the target’s id and the label the Jump list, title and tabs show.
type SectionBarSection = {
/** The section's element `id`; its first heading takes focus when the section is followed. */
id: string;
/** The label, authored in sentence case. */
label: React.ReactNode;
}Specification: DESIGN-SYSTEM.md §11.7 (sticky section bar) and D183, D184 (sticky thresholds; the desktop bar, tab strip and guide bar).