Section
A page-scale band: a full-bleed hairline marks the section boundary while the content stays on the page container.
Installation
$
Usage
import { Section } from "@/components/ui/section"Examples
Default
Each band draws its own closing hairline. The last band before a footer turns it off, so two rules do not stack.
Opening band
padTop lifts the top pad alone, for a hero that needs more air above it than below.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| pad | "region" | "section" | "region" | Vertical breathing inside the band. region is the page tempo; section opens or closes the page. |
| padTop | "region" | "section" | - | Overrides the top pad alone. |
| divider | boolean | true | The closing hairline. Turn it off on the last band before a footer that draws its own top edge, or the two rules stack. |
| container | boolean | true | Whether the band builds its own centred container. Off when the caller has already centred and padded. |
import * as React from "react"
import { cn } from "@/registry/lib/utils"
const SECTION_PAD = {
region: "var(--spacing-rhythm-region)",
section: "var(--spacing-rhythm-section)",
} as const
type SectionPad = keyof typeof SECTION_PAD
// @use-when a whole page section or sub-section, carrying its own vertical
// rhythm and an optional divider.
function Section({
pad = "region",
padTop,
divider = true,
container = true,
className,
style,
children,
...props
}: React.ComponentProps<"section"> & {
/** Vertical breathing inside the band. `region` is the default page tempo;
* `section` is for a band that opens or closes the page. */
pad?: SectionPad
/** Override the top pad alone, for a first band that needs more air above it
* than below. */
padTop?: SectionPad
/** The hairline closing the band. Turn it off on the last band before a
* footer that draws its own top edge, or the two rules stack. */
divider?: boolean
/** Whether the band builds its own centred container. Off when the caller
* has already centred and padded, the same call `SiteHeader` offers. */
container?: boolean
}) {
const inner = container ? (
<div className="mx-auto max-w-chrome px-chrome-gutter">{children}</div>
) : (
children
)
return (
<section
data-slot="section"
className={cn(divider && "border-b border-border", className)}
style={{
paddingBlock: SECTION_PAD[pad],
...(padTop ? { paddingTop: SECTION_PAD[padTop] } : null),
...style,
}}
{...props}
>
{inner}
</section>
)
}
// A page-scale band: the hairline runs edge to edge of the VIEWPORT while the
// content stays on the page container. Same shell SiteHeader and SiteFooter
// already use, and structured that way rather than breaking a rule out of the
// container with `w-screen` on purpose: `100vw` counts the scrollbar gutter, so
// a broken-out rule overhangs by the scrollbar width and adds horizontal
// scroll. An `Hr` cannot do this job either, since its `container` variant caps
// at `--container` rather than the viewport.
//
// The RULE is the section boundary, which is what lets the band pad `region`
// (60) a side instead of leaving `section` (96) of blank: two sections' content
// then sits 60 + rule + 60 apart, and the boundary is legible at a glance
// rather than inferred from the size of a gap.
//
// Padding is an inline style reading the token, never a `py-*` class: Tailwind
// v4 inlines spacing utility values at compile time, so a class would freeze
// the length past any consumer token override (same reason Stack and
// PageHeader apply their gaps this way).
export { Section, SECTION_PAD, type SectionPad }