Compares a value across categories with bars from a zero baseline.
Section
A story section: a numbered step marker or a headline, introductory prose, then one or more exhibits in a responsive one- to three-column grid.
- Use when
- The building block of a longer piece. Each section states one fact with its number in the title, explains it in a paragraph and shows it in one to three exhibits. Use
stepwhen the sections are read in order. - Avoid when
- The opening of a piece (use hero figure); a single chart with one callout (use chart with callout directly); more than three exhibits in one section (split it).
Preview
0204First-quarter churn was 2.0% for customers who joined after March
Monthly churn fell from 4.2% in January to 2.1% in December 2024. The new onboarding launched in March. Customers who joined after March had first-quarter churn of 2.0%. Customers who joined before March had 4.1%.
Monthly churn fell from 4.2% to 2.1% in 2024
Monthly customer churn, 2024
First-quarter churn was 4.1% for customers who joined before March
First-quarter churn by sign-up cohort, 2024
The next section covers the first 90 days after sign-up.
import { Section, registerAll } from "insightcharts";
// The slot props below are JSON specs. This makes their `type` names resolvable.
registerAll();
export function Example() {
return (
<Section
step={2}
total={4}
title="First-quarter churn was 2.0% for customers who joined after March"
intro="Monthly churn fell from 4.2% in January to 2.1% in December 2024. The new onboarding launched in March. Customers who joined after March had first-quarter churn of **2.0%**. Customers who joined before March had 4.1%."
columns={2}
body={[
{
type: "line-chart",
data: [
{ month: "2024-01-01", rate: 4.2 },
{ month: "2024-02-01", rate: 4.3 },
{ month: "2024-03-01", rate: 4.1 },
{ month: "2024-04-01", rate: 3.6 },
{ month: "2024-05-01", rate: 3.1 },
{ month: "2024-06-01", rate: 2.9 },
{ month: "2024-07-01", rate: 2.6 },
{ month: "2024-08-01", rate: 2.4 },
{ month: "2024-09-01", rate: 2.3 },
{ month: "2024-10-01", rate: 2.2 },
{ month: "2024-11-01", rate: 2.1 },
{ month: "2024-12-01", rate: 2.1 },
],
x: "month",
y: "rate",
format: "{value}%",
zero: true,
height: 240,
title: "Monthly churn fell from 4.2% to 2.1% in 2024",
subtitle: "Monthly customer churn, 2024",
annotations: [
{ type: "line", axis: "x", value: "2024-03-01", label: "New onboarding" },
],
},
{
type: "bar-chart",
data: [
{ cohort: "Before March", churn: 4.1 },
{ cohort: "After March", churn: 2 },
],
x: "cohort",
y: "churn",
format: "{value}%",
height: 240,
highlight: "After March",
title: "First-quarter churn was 4.1% for customers who joined before March",
subtitle: "First-quarter churn by sign-up cohort, 2024",
},
]}
outro="The next section covers the first 90 days after sign-up."
/>
);
}{
"type": "section",
"step": 2,
"total": 4,
"title": "First-quarter churn was 2.0% for customers who joined after March",
"intro": "Monthly churn fell from 4.2% in January to 2.1% in December 2024. The new onboarding launched in March. Customers who joined after March had first-quarter churn of **2.0%**. Customers who joined before March had 4.1%.",
"columns": 2,
"body": [
{
"type": "line-chart",
"data": [
{ "month": "2024-01-01", "rate": 4.2 },
{ "month": "2024-02-01", "rate": 4.3 },
{ "month": "2024-03-01", "rate": 4.1 },
{ "month": "2024-04-01", "rate": 3.6 },
{ "month": "2024-05-01", "rate": 3.1 },
{ "month": "2024-06-01", "rate": 2.9 },
{ "month": "2024-07-01", "rate": 2.6 },
{ "month": "2024-08-01", "rate": 2.4 },
{ "month": "2024-09-01", "rate": 2.3 },
{ "month": "2024-10-01", "rate": 2.2 },
{ "month": "2024-11-01", "rate": 2.1 },
{ "month": "2024-12-01", "rate": 2.1 }
],
"x": "month",
"y": "rate",
"format": "{value}%",
"zero": true,
"height": 240,
"title": "Monthly churn fell from 4.2% to 2.1% in 2024",
"subtitle": "Monthly customer churn, 2024",
"annotations": [
{
"type": "line",
"axis": "x",
"value": "2024-03-01",
"label": "New onboarding"
}
]
},
{
"type": "bar-chart",
"data": [
{ "cohort": "Before March", "churn": 4.1 },
{ "cohort": "After March", "churn": 2 }
],
"x": "cohort",
"y": "churn",
"format": "{value}%",
"height": 240,
"highlight": "After March",
"title": "First-quarter churn was 4.1% for customers who joined before March",
"subtitle": "First-quarter churn by sign-up cohort, 2024"
}
],
"outro": "The next section covers the first 90 days after sign-up."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Composition
Built from
The pieces section renders or expects in its slots.
Atoms
6The opening block of a piece or section: kicker, title, one-sentence deck and a byline/date line.
Shows how one or more values change over time or another ordered dimension.
A readable block of narration at a 45-75 character measure, for the text between charts.
A compact, bordered KPI tile: label, value, change against a baseline and an optional trend graphic.
A numbered chapter or step heading ("01 — Electricity sources in 2015") for sequencing the sections of a story.
Used in
No template lists section in its composes. It still fits any slot that takes a component.
Installation
$ pnpm dlx shadcn@latest add @insightcharts/sectionThe CLI copies the source into your project and needs the @insightcharts registry in components.json; see installation. With the package, import it by name:
import { Section } from "insightcharts";Examples
Unnumbered, one exhibit, with a rule
Retention
Monthly churn was between 2.1% and 2.4% from August to December 2024
It was 4.2% in January.
import { Section, registerAll } from "insightcharts";
// The slot props below are JSON specs. This makes their `type` names resolvable.
registerAll();
export function Example() {
return (
<Section
rule
kicker="Retention"
title="Monthly churn was between 2.1% and 2.4% from August to December 2024"
deck="It was 4.2% in January."
body={{
type: "line-chart",
data: [
{ month: "2024-01-01", rate: 4.2 },
{ month: "2024-02-01", rate: 4.3 },
{ month: "2024-03-01", rate: 4.1 },
{ month: "2024-04-01", rate: 3.6 },
{ month: "2024-05-01", rate: 3.1 },
{ month: "2024-06-01", rate: 2.9 },
{ month: "2024-07-01", rate: 2.6 },
{ month: "2024-08-01", rate: 2.4 },
{ month: "2024-09-01", rate: 2.3 },
{ month: "2024-10-01", rate: 2.2 },
{ month: "2024-11-01", rate: 2.1 },
{ month: "2024-12-01", rate: 2.1 },
],
x: "month",
y: "rate",
format: "{value}%",
zero: true,
height: 260,
}}
/>
);
}{
"type": "section",
"rule": true,
"kicker": "Retention",
"title": "Monthly churn was between 2.1% and 2.4% from August to December 2024",
"deck": "It was 4.2% in January.",
"body": {
"type": "line-chart",
"data": [
{ "month": "2024-01-01", "rate": 4.2 },
{ "month": "2024-02-01", "rate": 4.3 },
{ "month": "2024-03-01", "rate": 4.1 },
{ "month": "2024-04-01", "rate": 3.6 },
{ "month": "2024-05-01", "rate": 3.1 },
{ "month": "2024-06-01", "rate": 2.9 },
{ "month": "2024-07-01", "rate": 2.6 },
{ "month": "2024-08-01", "rate": 2.4 },
{ "month": "2024-09-01", "rate": 2.3 },
{ "month": "2024-10-01", "rate": 2.2 },
{ "month": "2024-11-01", "rate": 2.1 },
{ "month": "2024-12-01", "rate": 2.1 }
],
"x": "month",
"y": "rate",
"format": "{value}%",
"zero": true,
"height": 260
}
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Three columns of figures
Product metrics, December 2024
import { Section, registerAll } from "insightcharts";
// The slot props below are JSON specs. This makes their `type` names resolvable.
registerAll();
export function Example() {
return (
<Section
title="Product metrics, December 2024"
columns={3}
spacing="compact"
body={[
{
type: "stat-tile",
label: "Monthly churn",
value: "2.1%",
delta: "−2.1 pts",
deltaLabel: "vs January",
upIsGood: false,
},
{
type: "stat-tile",
label: "Monthly active users",
value: 48210,
format: "compact",
delta: 0.265,
deltaLabel: "vs January",
},
{
type: "stat-tile",
label: "Net revenue retention",
value: 1.12,
format: "percent",
context: "Top quartile is 110%",
},
]}
/>
);
}{
"type": "section",
"title": "Product metrics, December 2024",
"columns": 3,
"spacing": "compact",
"body": [
{
"type": "stat-tile",
"label": "Monthly churn",
"value": "2.1%",
"delta": "−2.1 pts",
"deltaLabel": "vs January",
"upIsGood": false
},
{
"type": "stat-tile",
"label": "Monthly active users",
"value": 48210,
"format": "compact",
"delta": 0.265,
"deltaLabel": "vs January"
},
{
"type": "stat-tile",
"label": "Net revenue retention",
"value": 1.12,
"format": "percent",
"context": "Top quartile is 110%"
}
]
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | The section's main fact, as one plain sentence with its number. | |
| step | number | string | Step number: turns the heading into a numbered chapter marker ("02 — ..."). | |
| total | number | Total steps, shown as "02 / 05". | |
| kicker | string | Small label above an unnumbered heading. | |
| deck | string | One plain sentence under the heading that adds a second fact. | |
| intro | string | string[] | Introductory narration. Supports **bold**, *italic* and links. | |
| body | Slot | Slot[] | The exhibits: one slot or several, each a spec or a React element. | |
| columns | 1 | 2 | 3 | 1 | Columns for the body at full width; reduced under 720px and 520px. |
| outro | string | string[] | Closing narration under the body. | |
| rule | boolean | false | Hairline above the section. |
| level | 1 | 2 | 3 | 4 | 5 | 6 | 2 | Heading level. |
| id | string | Anchor id for deep links. | |
| spacing | "compact" | "comfortable" | "comfortable" | Vertical rhythm between heading, intro and body. |
| children | ReactNode | JSX alternative to body; each child becomes a grid item. | |
| theme | ThemeInput | Theme override, passed on to everything inside. | |
| mode | "auto" | "system" | "light" | "dark" | "auto" | Colour mode. |