Components · Layout
Section header
The head of a page section: a kicker in words, the heading, a line or two and an optional action.
71% of the 2,062 sites in The UI Index have a header (1,458 sites).
Changelog
What shipped this month
Twelve releases, two new components and a faster build.
Pricing
One plan per team size
Start free and pay when the second person joins.
Changelog
What shipped this month
Twelve releases, two new components and a faster build.
Pricing
One plan per team size
Start free and pay when the second person joins.
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ section-header.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ section-header.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS.
import type { ReactNode } from 'react';
import { cn } from '@/lib/utils';
import { sectionHeader as c, type SectionHeaderContent } from './section-header.variants';
export type SectionHeaderProps = SectionHeaderContent & { className?: string; children?: ReactNode };
/** The head of a page section: kicker, heading, a line or two, and an optional action. */
export function SectionHeader({ title, kicker, description, align = 'start', id, as: Heading = 'h2', className, children }: SectionHeaderProps) {
return (
<header className={cn(c.root, className)}>
<div className={align === 'center' ? c.center : c.start}>
<div>
{kicker && <p className={c.kicker}>{kicker}</p>}
<Heading id={id} className={c.title}>{title}</Heading>
{description && <p className={cn(c.description, align === 'center' && 'mx-auto')}>{description}</p>}
</div>
{children && <div className={c.action}>{children}</div>}
</div>
</header>
);
}/** The head of a page section (a "section header" in The UI Index): an optional kicker in words (never a number), the
* heading, one or two lines, and an optional action at the end. start: on the page's left edge, the action beside
* the text from a wide container. center: centred, the text kept to two lines' measure. */
export const sectionHeader = {
root: '@container w-full',
start: 'flex flex-col gap-4 @2xl:flex-row @2xl:items-end @2xl:justify-between',
center: 'mx-auto flex max-w-2xl flex-col items-center gap-4 text-center',
kicker: 'text-sm font-medium text-muted-foreground',
title: 'mt-2 text-3xl font-semibold tracking-[-0.03em] text-balance @2xl:text-4xl',
description: 'mt-3 max-w-xl text-base text-pretty text-muted-foreground',
action: 'shrink-0',
};
export type SectionHeaderContent = { title: string; kicker?: string; description?: string; align?: 'start' | 'center'; id?: string; as?: 'h2' | 'h3' };---
import { cn } from '@/lib/utils';
import { sectionHeader as c, type SectionHeaderContent } from './section-header.variants';
type Props = SectionHeaderContent & { class?: string };
const { title, kicker, description, align = 'start', id, as: Heading = 'h2', class: className } = Astro.props;
---
<header class={cn(c.root, className)}>
<div class={align === 'center' ? c.center : c.start}>
<div>
{kicker && <p class={c.kicker}>{kicker}</p>}
<Heading id={id} class={c.title}>{title}</Heading>
{description && <p class={cn(c.description, align === 'center' && 'mx-auto')}>{description}</p>}
</div>
{Astro.slots.has('default') && <div class={c.action}><slot /></div>}
</div>
</header>/** The head of a page section (a "section header" in The UI Index): an optional kicker in words (never a number), the
* heading, one or two lines, and an optional action at the end. start: on the page's left edge, the action beside
* the text from a wide container. center: centred, the text kept to two lines' measure. */
export const sectionHeader = {
root: '@container w-full',
start: 'flex flex-col gap-4 @2xl:flex-row @2xl:items-end @2xl:justify-between',
center: 'mx-auto flex max-w-2xl flex-col items-center gap-4 text-center',
kicker: 'text-sm font-medium text-muted-foreground',
title: 'mt-2 text-3xl font-semibold tracking-[-0.03em] text-balance @2xl:text-4xl',
description: 'mt-3 max-w-xl text-base text-pretty text-muted-foreground',
action: 'shrink-0',
};
export type SectionHeaderContent = { title: string; kicker?: string; description?: string; align?: 'start' | 'center'; id?: string; as?: 'h2' | 'h3' };<div class="grid w-full max-w-4xl gap-14">
<header class="@container w-full">
<div class="flex flex-col gap-4 @2xl:flex-row @2xl:items-end @2xl:justify-between">
<div>
<p class="text-sm font-medium text-muted-foreground">Changelog</p>
<h2 class="mt-2 text-3xl font-semibold tracking-[-0.03em] text-balance @2xl:text-4xl">What shipped this month</h2>
<p class="mt-3 max-w-xl text-base text-pretty text-muted-foreground">Twelve releases, two new components and a faster build.</p>
</div>
<div class="shrink-0">
<a href="#" class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground active:bg-pressed h-9 px-4">
All releases
</a>
</div>
</div>
</header>
<header class="@container w-full">
<div class="mx-auto flex max-w-2xl flex-col items-center gap-4 text-center">
<div>
<p class="text-sm font-medium text-muted-foreground">Pricing</p>
<h2 class="mt-2 text-3xl font-semibold tracking-[-0.03em] text-balance @2xl:text-4xl">One plan per team size</h2>
<p class="mt-3 max-w-xl text-base text-pretty text-muted-foreground mx-auto">Start free and pay when the second person joins.</p>
</div>
</div>
</header>
</div>