An inline change indicator: direction arrow, signed value and an optional baseline label.
Comparison table
A table whose rows carry inline sparklines, deltas and in-cell bars beside their exact values, from rows and a column config.
- Use when
- Comparing five to twenty entities on a level, a trend and a change at once: countries, products, teams. Readers look up their row. The sparkline and delta show how the value changed.
- Avoid when
- Plain lookups with no series (use data table); one measure ranked (use ranked list or bar chart); trends the reader must compare precisely (use small multiples). Each sparkline has its own scale, so it shows the direction of change and cannot be used to compare levels between rows.
Preview
| 2019 to 2024 | ||||
|---|---|---|---|---|
| Germany | 498 | 58.9% | +18.8 pts | |
| Spain | 264 | 56.8% | +19.3 pts | |
| Netherlands | 121 | 52.4% | +34.2 pts | |
| Italy | 264 | 49% | +9.1 pts | |
| Poland | 167 | 29.5% | +15.4 pts | |
| France | 536 | 28.3% | +7.4 pts |
Source: Ember, European Electricity Review 2025 · Rounded, illustrative
import { ComparisonTable } from "insightcharts";
const data = [
{
"2019": 40.1,
"2020": 44.2,
"2021": 40.9,
"2022": 44.5,
"2023": 52.6,
"2024": 58.9,
country: "Germany",
share: 58.9,
twh: 498,
},
{
"2019": 37.5,
"2020": 44.1,
"2021": 46.7,
"2022": 42.2,
"2023": 50.3,
"2024": 56.8,
country: "Spain",
share: 56.8,
twh: 264,
},
{
"2019": 18.2,
"2020": 26.1,
"2021": 33,
"2022": 40.1,
"2023": 47.7,
"2024": 52.4,
country: "Netherlands",
share: 52.4,
twh: 121,
},
{
"2019": 39.9,
"2020": 41.7,
"2021": 40.3,
"2022": 35.6,
"2023": 43.8,
"2024": 49,
country: "Italy",
share: 49,
twh: 264,
},
{
"2019": 20.9,
"2020": 24.2,
"2021": 22.5,
"2022": 24.4,
"2023": 27,
"2024": 28.3,
country: "France",
share: 28.3,
twh: 536,
},
{
"2019": 14.1,
"2020": 17.2,
"2021": 16.9,
"2022": 20.6,
"2023": 26.4,
"2024": 29.5,
country: "Poland",
share: 29.5,
twh: 167,
},
];
export function Example() {
return (
<ComparisonTable
title="The Netherlands' renewable share of electricity rose from 18.2% to 52.4% between 2019 and 2024"
subtitle="Renewable share of electricity generation, selected EU countries, 2019 to 2024"
data={data}
columns={[
{ key: "country", label: "Country" },
{
key: "twh",
label: "Generation, TWh",
kind: "bar",
format: "integer",
hideBelow: 560,
},
{ key: "share", label: "Share, 2024", format: "{value}%" },
{
key: "trend",
label: "2019 to 2024",
kind: "sparkline",
fields: ["2019", "2020", "2021", "2022", "2023", "2024"],
format: "{value}%",
},
{
key: "change",
label: "Change",
kind: "delta",
fields: ["2019", "2020", "2021", "2022", "2023", "2024"],
change: "absolute",
format: "{value} pts",
},
]}
sort={{ key: "share" }}
highlight="Netherlands"
source="Ember, European Electricity Review 2025"
note="Rounded, illustrative"
/>
);
}{
"type": "comparison-table",
"title": "The Netherlands' renewable share of electricity rose from 18.2% to 52.4% between 2019 and 2024",
"subtitle": "Renewable share of electricity generation, selected EU countries, 2019 to 2024",
"data": [
{
"2019": 40.1,
"2020": 44.2,
"2021": 40.9,
"2022": 44.5,
"2023": 52.6,
"2024": 58.9,
"country": "Germany",
"share": 58.9,
"twh": 498
},
{
"2019": 37.5,
"2020": 44.1,
"2021": 46.7,
"2022": 42.2,
"2023": 50.3,
"2024": 56.8,
"country": "Spain",
"share": 56.8,
"twh": 264
},
{
"2019": 18.2,
"2020": 26.1,
"2021": 33,
"2022": 40.1,
"2023": 47.7,
"2024": 52.4,
"country": "Netherlands",
"share": 52.4,
"twh": 121
},
{
"2019": 39.9,
"2020": 41.7,
"2021": 40.3,
"2022": 35.6,
"2023": 43.8,
"2024": 49,
"country": "Italy",
"share": 49,
"twh": 264
},
{
"2019": 20.9,
"2020": 24.2,
"2021": 22.5,
"2022": 24.4,
"2023": 27,
"2024": 28.3,
"country": "France",
"share": 28.3,
"twh": 536
},
{
"2019": 14.1,
"2020": 17.2,
"2021": 16.9,
"2022": 20.6,
"2023": 26.4,
"2024": 29.5,
"country": "Poland",
"share": 29.5,
"twh": 167
}
],
"columns": [
{ "key": "country", "label": "Country" },
{
"key": "twh",
"label": "Generation, TWh",
"kind": "bar",
"format": "integer",
"hideBelow": 560
},
{ "key": "share", "label": "Share, 2024", "format": "{value}%" },
{
"key": "trend",
"label": "2019 to 2024",
"kind": "sparkline",
"fields": ["2019", "2020", "2021", "2022", "2023", "2024"],
"format": "{value}%"
},
{
"key": "change",
"label": "Change",
"kind": "delta",
"fields": ["2019", "2020", "2021", "2022", "2023", "2024"],
"change": "absolute",
"format": "{value} pts"
}
],
"sort": { "key": "share" },
"highlight": "Netherlands",
"source": "Ember, European Electricity Review 2025",
"note": "Rounded, illustrative"
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Composition
Built from
The pieces comparison table renders or expects in its slots.
Atoms
2A word-sized line or bar series without axes, with the latest point marked.
Used in
1 template uses comparison table.
Templates
1A recurring report. It has a masthead with the report name and period, a headline that states the main change, a KPI grid with changes and sparklines, a ranked list beside one or two trend charts, an optional comparison table, short notes and a source note.
Installation
$ pnpm dlx shadcn@latest add @insightcharts/comparison-tableThe 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 { ComparisonTable } from "insightcharts";Examples
Long data with groupBy, relative change, compact
| Trend | ||||
|---|---|---|---|---|
| Germany | 58.9% | 58.9% | +46.9% | |
| Spain | 56.8% | 56.8% | +51.5% | |
| Netherlands | 52.4% | 52.4% | +187.9% | |
| Italy | 49% | 49% | +22.8% | |
| France | 28.3% | 28.3% | +35.4% | |
| Poland | 29.5% | 29.5% | +109.2% |
Illustrative data
import { ComparisonTable } from "insightcharts";
const data = [
{ country: "Germany", year: "2019", share: 40.1 },
{ country: "Germany", year: "2020", share: 44.2 },
{ country: "Germany", year: "2021", share: 40.9 },
{ country: "Germany", year: "2022", share: 44.5 },
{ country: "Germany", year: "2023", share: 52.6 },
{ country: "Germany", year: "2024", share: 58.9 },
{ country: "Spain", year: "2019", share: 37.5 },
{ country: "Spain", year: "2020", share: 44.1 },
{ country: "Spain", year: "2021", share: 46.7 },
{ country: "Spain", year: "2022", share: 42.2 },
{ country: "Spain", year: "2023", share: 50.3 },
{ country: "Spain", year: "2024", share: 56.8 },
{ country: "Netherlands", year: "2019", share: 18.2 },
{ country: "Netherlands", year: "2020", share: 26.1 },
{ country: "Netherlands", year: "2021", share: 33 },
{ country: "Netherlands", year: "2022", share: 40.1 },
{ country: "Netherlands", year: "2023", share: 47.7 },
{ country: "Netherlands", year: "2024", share: 52.4 },
{ country: "Italy", year: "2019", share: 39.9 },
{ country: "Italy", year: "2020", share: 41.7 },
{ country: "Italy", year: "2021", share: 40.3 },
{ country: "Italy", year: "2022", share: 35.6 },
{ country: "Italy", year: "2023", share: 43.8 },
{ country: "Italy", year: "2024", share: 49 },
{ country: "France", year: "2019", share: 20.9 },
{ country: "France", year: "2020", share: 24.2 },
{ country: "France", year: "2021", share: 22.5 },
{ country: "France", year: "2022", share: 24.4 },
{ country: "France", year: "2023", share: 27 },
{ country: "France", year: "2024", share: 28.3 },
{ country: "Poland", year: "2019", share: 14.1 },
{ country: "Poland", year: "2020", share: 17.2 },
{ country: "Poland", year: "2021", share: 16.9 },
{ country: "Poland", year: "2022", share: 20.6 },
{ country: "Poland", year: "2023", share: 26.4 },
{ country: "Poland", year: "2024", share: 29.5 },
];
export function Example() {
return (
<ComparisonTable
data={data}
groupBy="country"
density="compact"
columns={[
{ key: "country", label: "Country" },
{ key: "share", label: "Latest", format: "{value}%" },
{ key: "share", label: "Peak", agg: "max", format: "{value}%", hideBelow: 420 },
{
key: "share",
label: "Trend",
kind: "sparkline",
mark: "bar",
format: "{value}%",
},
{ key: "share", label: "Since 2019", kind: "delta" },
]}
note="Illustrative data"
/>
);
}{
"type": "comparison-table",
"data": [
{ "country": "Germany", "year": "2019", "share": 40.1 },
{ "country": "Germany", "year": "2020", "share": 44.2 },
{ "country": "Germany", "year": "2021", "share": 40.9 },
{ "country": "Germany", "year": "2022", "share": 44.5 },
{ "country": "Germany", "year": "2023", "share": 52.6 },
{ "country": "Germany", "year": "2024", "share": 58.9 },
{ "country": "Spain", "year": "2019", "share": 37.5 },
{ "country": "Spain", "year": "2020", "share": 44.1 },
{ "country": "Spain", "year": "2021", "share": 46.7 },
{ "country": "Spain", "year": "2022", "share": 42.2 },
{ "country": "Spain", "year": "2023", "share": 50.3 },
{ "country": "Spain", "year": "2024", "share": 56.8 },
{ "country": "Netherlands", "year": "2019", "share": 18.2 },
{ "country": "Netherlands", "year": "2020", "share": 26.1 },
{ "country": "Netherlands", "year": "2021", "share": 33 },
{ "country": "Netherlands", "year": "2022", "share": 40.1 },
{ "country": "Netherlands", "year": "2023", "share": 47.7 },
{ "country": "Netherlands", "year": "2024", "share": 52.4 },
{ "country": "Italy", "year": "2019", "share": 39.9 },
{ "country": "Italy", "year": "2020", "share": 41.7 },
{ "country": "Italy", "year": "2021", "share": 40.3 },
{ "country": "Italy", "year": "2022", "share": 35.6 },
{ "country": "Italy", "year": "2023", "share": 43.8 },
{ "country": "Italy", "year": "2024", "share": 49 },
{ "country": "France", "year": "2019", "share": 20.9 },
{ "country": "France", "year": "2020", "share": 24.2 },
{ "country": "France", "year": "2021", "share": 22.5 },
{ "country": "France", "year": "2022", "share": 24.4 },
{ "country": "France", "year": "2023", "share": 27 },
{ "country": "France", "year": "2024", "share": 28.3 },
{ "country": "Poland", "year": "2019", "share": 14.1 },
{ "country": "Poland", "year": "2020", "share": 17.2 },
{ "country": "Poland", "year": "2021", "share": 16.9 },
{ "country": "Poland", "year": "2022", "share": 20.6 },
{ "country": "Poland", "year": "2023", "share": 26.4 },
{ "country": "Poland", "year": "2024", "share": 29.5 }
],
"groupBy": "country",
"density": "compact",
"columns": [
{ "key": "country", "label": "Country" },
{ "key": "share", "label": "Latest", "format": "{value}%" },
{
"key": "share",
"label": "Peak",
"agg": "max",
"format": "{value}%",
"hideBelow": 420
},
{
"key": "share",
"label": "Trend",
"kind": "sparkline",
"mark": "bar",
"format": "{value}%"
},
{ "key": "share", "label": "Since 2019", "kind": "delta" }
],
"note": "Illustrative data"
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Deltas given in the data, down is good
| Last six weeks | |||
|---|---|---|---|
| Onboarding | 1 h 05 min | −54% | |
| Technical support | 2 h 14 min | −46% | |
| Billing | 3 h 40 min | +19% |
Illustrative data
import { ComparisonTable } from "insightcharts";
const data = [
{
team: "Onboarding",
median: "1 h 05 min",
weekly: [2.4, 2.1, 1.9, 1.6, 1.3, 1.1],
change: -0.54,
},
{
team: "Technical support",
median: "2 h 14 min",
weekly: [4.1, 3.8, 3.2, 2.9, 2.4, 2.2],
change: -0.46,
},
{
team: "Billing",
median: "3 h 40 min",
weekly: [3.1, 3, 3.3, 3.4, 3.6, 3.7],
change: 0.19,
},
];
export function Example() {
return (
<ComparisonTable
title="Billing's first response time rose 19% over six weeks"
data={data}
columns={[
{ key: "team", label: "Team" },
{ key: "median", label: "Median first response", align: "right" },
{ key: "weekly", label: "Last six weeks" },
{ key: "change", label: "Change", kind: "delta", upIsGood: false },
]}
note="Illustrative data"
/>
);
}{
"type": "comparison-table",
"title": "Billing's first response time rose 19% over six weeks",
"data": [
{
"team": "Onboarding",
"median": "1 h 05 min",
"weekly": [2.4, 2.1, 1.9, 1.6, 1.3, 1.1],
"change": -0.54
},
{
"team": "Technical support",
"median": "2 h 14 min",
"weekly": [4.1, 3.8, 3.2, 2.9, 2.4, 2.2],
"change": -0.46
},
{
"team": "Billing",
"median": "3 h 40 min",
"weekly": [3.1, 3, 3.3, 3.4, 3.6, 3.7],
"change": 0.19
}
],
"columns": [
{ "key": "team", "label": "Team" },
{ "key": "median", "label": "Median first response", "align": "right" },
{ "key": "weekly", "label": "Last six weeks" },
{ "key": "change", "label": "Change", "kind": "delta", "upIsGood": false }
],
"note": "Illustrative data"
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data | DataInput | Wide data: one row per entity. Long data: one row per entity and period, with groupBy. Omit inside <InsightData>. | |
| transform | Transform | Transform[] | Applied before rows are grouped. | |
| columnsrequired | (string | { key, label?, kind?, fields?, agg?, change?, format?, upIsGood?, mark?, align?, width?, hideBelow?, sortable? })[] | Columns in order; the first is the row header. kind is text, number, sparkline, delta or bar. For wide data give a sparkline or computed delta its series with fields; for long data they use the group's rows. | |
| groupBy | string | Long data: the entity field. Rows sharing a value become one table row, in data order. | |
| highlight | string | string[] | Entities to emphasise, matched on the first column. | |
| sort | { key, direction?: "asc" | "desc" } | Order rows by a column's value. Default direction: descending. With sortable this is the opening order. | |
| sortable | boolean | true | Click or press Enter on a column header to sort by it (numbers largest first, text A to Z); a second press reverses and a third restores the original order. Sparkline columns do not sort; set sortable: false on a column to exclude it. |
| limit | number | Show only the first N rows. | |
| density | "comfortable" | "compact" | "comfortable" | Row padding. |
| title | string | The main fact with its number, or a plain label. Set as the table's <caption>. | |
| subtitle | string | Units and period. | |
| source | string | Shown under the table as "Source: ...". | |
| note | string | Method note or caveat. | |
| theme | ThemeInput | Theme override, passed on to the sparklines and deltas. | |
| mode | "auto" | "system" | "light" | "dark" | "auto" | Colour mode. |