Section
StableA page band with an eyebrow, an h2 and a lead, on a plain or sunken ground. SectionHeading is the heading block alone.
Section is a whole band: a <section> named by its own <h2>, generous space above and below, a Wrap inside, and an optional sunken grey ground with grain. SectionHeading, from the home pages, is just the heading block, a little larger and left or centred, for sections you build yourself. Eyebrow is the small mono label above both; each module has its own.
Plain and sunken
import type { ReactNode } from 'react'import { Section } from '@rchursin/product/shell/layout'
/** * Two sections back to back: plain, then sunken. Each one is a <section> named by its own <h2>. * The dashed outlines and corner tags are only here to show where each section starts and ends. */export default function SectionTones() { return ( <div className="w-full"> <Section id="section-demo-plain" eyebrow="01 · Plain" title="Sections set the rhythm of a page." lead="Generous space above and below, and a heading block that never grows wider than a comfortable line." className="relative outline-1 -outline-offset-1 outline-line-strong outline-dashed" > <Tag>tone="plain"</Tag> <Content /> </Section>Usage
import { Eyebrow, Section } from '@rchursin/product/shell/layout'import { SectionHeading } from '@rchursin/home/ui'<Section id="parts" tone="sunken" eyebrow="Parts" title="Every piece, in every state."> <Parts /></Section>Examples
SectionHeading
Left or centred. Pass id and point your section’s aria-labelledby at it.
import { SectionHeading } from '@rchursin/home/ui'
/** * SectionHeading is the heading block on its own, for sections you build yourself. * Pass `id` and point the section's aria-labelledby at it. */export default function SectionHeadingAlign() { return ( <div className="flex w-full flex-col gap-14 py-4"> <section aria-labelledby="heading-demo-left"> <SectionHeading id="heading-demo-left" eyebrow="The Library" title="Built with the parts it shows." lead="Every page of this site is made from the components documented here, these docs included." /> </section> <section aria-labelledby="heading-demo-center"> <SectionHeading id="heading-demo-center" align="center" eyebrow="Both themes" title="Black and white, in light and dark." lead="Semantic tokens flip with the theme, so kit parts need no dark variants of their own." /> </section> </div> )}Two eyebrows
The same label in two modules: the kit’s uses the theme token text-fg-3, the home pages’ the brand greys stone and fog.
import { Eyebrow as HomeEyebrow } from '@rchursin/home/ui'import { Eyebrow } from '@rchursin/product/shell/layout'
/** * Two Eyebrows with the same look. The kit's uses the theme token text-fg-3; the home pages' * uses the brand greys stone and fog. Import one per page. */export default function SectionEyebrows() { return ( <dl className="grid w-full max-w-md grid-cols-1 gap-6"> <div className="flex flex-col gap-2 border-t border-line pt-4"> <dt className="text-[13px] text-fg-2"> From <code className="font-mono text-xs">product/shell/layout</code> </dt> <dd> <Eyebrow>Product kit · Tasks</Eyebrow> </dd> </div> <div className="flex flex-col gap-2 border-t border-line pt-4"> <dt className="text-[13px] text-fg-2"> From <code className="font-mono text-xs">home/ui</code> </dt> <dd> <HomeEyebrow>Fig. 02 · Dead weight</HomeEyebrow> </dd> </div> </dl> )}API
Section props
| Prop | Type | Default | Description |
|---|---|---|---|
idrequired | string | – | The section’s id (its #anchor). The heading gets `${id}-title`, which names the section. |
eyebrow | string | – | A mono label above the title. |
titlerequired | ReactNode | – | The section’s <h2>. |
lead | ReactNode | – | A sentence or two under the title. |
childrenrequired | ReactNode | – | The section’s content, 40 to 48 pixels below the heading block. |
tone | 'plain' | 'sunken' | 'plain' | Sunken gives the section a quiet grey ground with film grain. Alternate them down a page. |
className | string | – | Extra classes on the <section>. |
SectionHeading props
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | ReactNode | – | The <h2>. It arrives out of a soft blur. |
eyebrow | ReactNode | – | A mono label above the title. |
lead | ReactNode | – | A paragraph under the title. |
id | string | – | Put on the <h2>, for your section’s aria-labelledby. |
align | 'left' | 'center' | 'left' | Centred also centres the block itself (max 672 pixels wide). |
className | string | – | Extra classes on the block. |
Eyebrow props (both)
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | – | A few words: where you are, or a figure number. |
className | string | – | Extra classes on the <p>, e.g. lux-reveal. |
Accessibility
- Section renders <section aria-labelledby="{id}-title"> with <h2 id="{id}-title">, so each one is a region landmark named by its heading.
- SectionHeading renders the <h2> but no <section>: wrap it yourself and point aria-labelledby at its id.
- Section has a 64 pixel scroll margin, so a #id link stops below a sticky header instead of under it.
- Eyebrows are <p> elements, not headings, so the page outline stays h1, then h2.
- The heading block arrives as it scrolls into view only when motion is allowed and a page root armed it (KitPage or useLuxe).
Guidelines
Do
- Alternate plain and sunken sections down a page.
- Write the title as a short sentence that says what the section shows.
- Give every Section a unique id: it is the anchor and it names the region.
Don’t
- Don’t put two sunken sections next to each other; the edge between them disappears.
- Don’t leave the key words only in the eyebrow; it is read as an ordinary paragraph.
- Don’t nest a Section inside another; it already brings its own column.
In use
Used on this site
Related
- ContainerA centred page column with a max width and side gutters. Container serves the home pages, Wrap the kit pages.
- Page introThe top of a kit page: an eyebrow, the page’s h1, a lead and a row of facts, over film grain and a soft ceiling light.
- SpecimenOne part on a quiet raised card. Specimen captions it underneath; Figure titles it on top.
- Scroll arrivalsContent that fades and rises in as it scrolls into view, bars that grow, stamps that land and a frame that draws itself.
Source
The component’s own files, exactly as they run on this site.
/** * Layout pieces for the kit pages: an intro, numbered sections, specimens (one component on a * card with a caption), a keyboard table and a short list of rules. * They share the site's finishing layer: arrivals (lux-reveal), hairlines, grain, the halo. */import type { ReactNode } from "react"import { cx } from "../../components/cx"import { useLuxe } from "../../components/motion"import { delay } from "../../home/ui"import { KeyCombo } from "../ui/button"
export function KitPage({ children, className }: { children: ReactNode; className?: string }) { const root = useLuxe<HTMLDivElement>() return ( <div ref={root} className={cx("bg-surface text-fg antialiased", className)}> {children} </div> )}