Section
Section
Page section with generous air above, the centered section heading and an optional label.
Text referenceSource and examples as JSON
npx -y github:anwarlemu/opencommerce-brand-cli#release add section --surface siteRun from the target React 19.2+ app root with Tailwind CSS 4 and the @/* source alias.
Usage
Use SiteSection for each band of the page and SectionHeading to open it. Pick width medium (800px) for features, pricing and questions, wide (1200px) for a table, the steps or the closing card. Add bottom padding only to the section that closes the page.
- A blue section
- Rules between sections
- Left-aligned section titles over centered content
- Bold titles
- Eyebrows in caps
- Two tinted sections in a row
Exports: SiteSection, SectionHeading, Eyebrow, SectionGround. Read the examples for composition and props.
SiteSection props: ground ("white" | "mist", default white), width ("wide" | "medium", default wide),
contained (default true), innerClassName, plus section props. SectionHeading props: title, description,
eyebrow, as ("h1" | "h2" | "h3", default h2), align ("center" | "start"). Eyebrow takes paragraph props.Example
import { SectionHeading, SiteSection } from "@/components/site/section";
import { Window } from "@/components/ui/window";
function Placeholder({ name }: { name: string }) {
return (
<Window name={name} className="h-48" bodyClassName="flex items-center justify-center font-mono text-caption text-fg-muted">
Content
</Window>
);
}
export function Site() {
return (
<>
<SiteSection width="medium">
<SectionHeading title="Everything your agent needs to buy" description="One endpoint for every store: search the catalogue, check out and track the order, with the uptime to rely on." />
<div className="grid gap-5 sm:grid-cols-2">
<Placeholder name="merchants.feature" />
<Placeholder name="checkout.feature" />
</div>
</SiteSection>
<SiteSection className="pb-18 md:pb-30">
<SectionHeading eyebrow="Monitoring" title="Merchant monitoring" description="Every route is exercised around the clock." />
<Placeholder name="monitoring.live" />
</SiteSection>
</>
);
}Source
import * as React from "react";
import { cn } from "@/lib/utils";
// A section of the page (the opencommerce site, .sec): generous air above (120px, 72px on phones) and nothing ruled;
// the next section's air is the gap, so a section has no padding below unless it closes the page (add pb-18
// md:pb-30). Content sits on the 1200px frame with 40px of gutter (20px on phones); `width="medium"` narrows it to the
// 800px column most sections use (features, pricing, the questions), `wide` keeps the frame (a live table, the steps,
// the closing card). The page is white throughout; `mist` is the one tint, for an inset band.
const GROUNDS = {
white: "bg-surface text-fg",
mist: "bg-subtle text-fg",
} as const;
const WIDTHS = {
wide: "w-(--ds-frame-column)",
medium: "w-(--ds-frame-column) max-w-(--ds-layout-medium)",
} as const;
type SectionGround = keyof typeof GROUNDS;
function SiteSection({
ground = "white",
width = "wide",
contained = true,
className,
innerClassName,
children,
...props
}: React.ComponentProps<"section"> & {
ground?: SectionGround;
/** The column: the 1200px frame (wide) or the 800px medium column. */
width?: keyof typeof WIDTHS;
/** Hold the content to the column. Off for a band whose content runs edge to edge. */
contained?: boolean;
innerClassName?: string;
}) {
return (
<section data-slot="site-section" data-ground={ground} className={cn("relative pt-18 md:pt-30", GROUNDS[ground], className)} {...props}>
{contained ? <div className={cn("mx-auto", WIDTHS[width], innerClassName)}>{children}</div> : children}
</section>
);
}
/** A small label over a title: the platform mono, small, in the brand blue. Sentence case, never caps. */
function Eyebrow({ className, ...props }: React.ComponentProps<"p">) {
return <p data-slot="eyebrow" className={cn("font-mono text-caption leading-body tracking-label text-fg-label", className)} {...props} />;
}
/**
* A section's opening, centered as the brand sets them: the 44px display title (32px on phones) tracked tight, and
* its 16px standfirst 16px below in muted ink, held to 576px. 48px (56px on wide screens) of air before the content.
*/
function SectionHeading({
title,
description,
eyebrow,
as: Heading = "h2",
align = "center",
className,
...props
}: Omit<React.ComponentProps<"div">, "title"> & {
title: React.ReactNode;
description?: React.ReactNode;
eyebrow?: React.ReactNode;
as?: "h1" | "h2" | "h3";
align?: "center" | "start";
}) {
const centered = align === "center";
return (
<div data-slot="section-heading" className={cn("pb-12 md:pb-14", centered ? "mx-auto max-w-(--ds-layout-heading) text-center" : "text-left", className)} {...props}>
{eyebrow && <Eyebrow className="mb-3">{eyebrow}</Eyebrow>}
<Heading className="font-display text-h2 leading-display font-medium tracking-tighter text-balance">{title}</Heading>
{description && <p className={cn("mt-4 max-w-(--ds-layout-text) text-body leading-lead text-pretty text-fg-muted", centered && "mx-auto")}>{description}</p>}
</div>
);
}
export { SiteSection, SectionHeading, Eyebrow };
export type { SectionGround };Reference: opencommerce-agentcard-web src/components/band.tsx, src/components/section-head.tsx, src/app/globals.css (.sec, .h-section, .lead).