---
title: Section
description: Kicker, heading and body, revealing top-down with an 80ms offset.
source: https://adamjurek.com/components/section
---

## Home


Section is a page section: a kicker, a heading and a body, wrapped in a
container and revealed top-down as the reader scrolls to it. Reach for it as
one of a page's top-level blocks whenever content should resolve as a
sequence instead of arriving as one block.

<!-- ::start:showcase demo="section" height="380" -->
<!-- ::end:showcase -->

## Why it is built this way

The heading and the body reveal separately, offset by 80 milliseconds. That
gap is the whole trick - short enough that it does not read as a deliberate
sequence, long enough that the two halves do not land on the same frame and
arrive as one flat block instead.


## Install

<!-- ::start:tabs -->

### TanStack

```shell
tanstack add https://adamjurek.com/r/tanstack/section.json
```

### shadcn

```shell
pnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/section.json
```

### pnpm

```shell
pnpm add @sushindustries/ui @sushindustries/atoms
```

<!-- ::end:tabs -->

## What you get

| | |
| --- | --- |
| Version | 0.1.0 |
| Category | layout · Page structure |
| Files | `section.tsx` |
| Dependencies | None |
| Also installs | `reveal` |
| Tags | heading, scroll, no-deps |

> [!NOTE] No runtime dependencies
> It brings nothing with it beyond the stylesheet.

## Get Started


Install commands are on Home, attached from the registry - they are not written
here, because a second copy is a copy that goes stale. This tab starts after the
install worked.

## Use it

```tsx
import { Section } from "@sushindustries/ui";

export function AboutSection() {
	return (
		<Section id="about" label="About" title="What this is">
			<p>One person builds this. The site is the library's first consumer.</p>
		</Section>
	);
}
```

## What you should see

A centred container with a small monospace kicker ("About"), an `h2` ("What
this is") under it, and the body below that - the heading fades and rises in
first, the body follows about 80ms later, once the section reaches the
viewport. On the very first render, before that observer fires, both are
present in the markup but invisible.

## If nothing happens

If the section stays invisible for good, check that it isn't sitting inside a
parent with `overflow: hidden` and no real height - that stops the `Reveal`s
inside it from ever reporting as intersecting. If it appears instantly with
no motion, `prefers-reduced-motion` is set, and that's correct.


## Guides


## Composing it

`Section` already provides the `.container` padding and the `.section`
block spacing - nest another `.container` inside it and the content gets
padded twice. It's meant to be used directly as one of a page's top-level
blocks, not as an inner wrapper.

## When not to use it

For the first thing on a page. `Section` reveals its heading and body on
scroll, which means content already in view on load spends a frame invisible
before the observer fires - fine below the fold, wrong for a hero or anything
that should be there the instant the page paints.


## API


<!-- generated:api -->

## Props

| Prop | Type | Default | Does |
| --- | --- | --- | --- |
| `id?` | `string` | - | Anchor target, so a nav can link straight to it. |
| `label?` | `string` | - | The small monospace kicker above the heading. |
| `title` | `string` | - |  |
| `children` | `ReactNode` | - |  |

<!-- /generated:api -->

## Notes

`title` is required and always renders as an `h2` - there's no way to get an
`h1` or `h3` out of this component. A page's own `h1` belongs to its `Hero`;
`Section` is for what comes after it.

The 80ms stagger between heading and body is fixed, not a prop. It exists to
keep the two halves off the same animation frame, not to be tuned per
section.


## Examples


Examples are the tab where the component is shown doing a job, not
demonstrating a prop. The API tab already lists the props.

<!-- ::start:showcase demo="section" height="420" -->
<!-- ::end:showcase -->

Press Compare. The frames are real viewports, so a layout that breaks at 320
breaks here too rather than in somebody's hands.

## In a page

```tsx
import { Section } from "@sushindustries/ui";

export function ComponentsPage() {
	return (
		<>
			<Section label="Packages" title="What's installable">
				<p>Every visible element on this site is a component in `ui`.</p>
			</Section>
			<Section
				id="conventions"
				label="Conventions"
				title="How a file gets placed"
			>
				<p>Kebab-case filenames, one component per file, flat.</p>
			</Section>
		</>
	);
}
```
