svelte-pivottable is a Svelte 5 pivot table library with drag-and-drop data exploration, grouping, and subtotals — similar to the pivot table experience in older versions of Microsoft Excel.
It is a Svelte port of react-pivottable-grouping, itself a fork of react-pivottable by Nicolas Kruchten, which is a React port of his jQuery-based PivotTable.js.
svelte-pivottable lets you summarize a dataset into a cross-tabulated table or a Plotly.js chart using a drag-and-drop interface. You can move attributes between rows, columns, filters, and aggregation slots interactively.
A live demo is available here.
npm install svelte-pivottable
PivotTableUI is the full drag-and-drop component. It manages its own UI state but delegates data rendering to PivotTable.
<script>
import { PivotTableUI } from 'svelte-pivottable';
const data = [
{ name: 'Alice', department: 'Engineering', salary: 95000 },
{ name: 'Bob', department: 'Marketing', salary: 72000 },
{ name: 'Carol', department: 'Engineering', salary: 105000 },
// ...
];
</script>
<PivotTableUI {data} rows={['department']} cols={[]} />
Data can also be provided as an array of arrays (first row is headers):
<script>
import { PivotTableUI } from 'svelte-pivottable';
const data = [
['name', 'department', 'salary'],
['Alice', 'Engineering', 95000],
['Bob', 'Marketing', 72000],
];
</script>
<PivotTableUI {data} />
PivotTable renders a saved configuration without any drag-and-drop UI. Use it for embedding fixed pivot views in dashboards.
<script>
import { PivotTable, TableRenderers, aggregators } from 'svelte-pivottable';
const data = [ /* ... */ ];
</script>
<PivotTable
{data}
rows={['department']}
cols={['year']}
aggregator={aggregators['Sum']}
vals={['salary']}
renderers={TableRenderers}
rendererName="Table Heatmap"
/>
TableRenderers is the default renderer set. Import it from svelte-pivottable or the svelte-pivottable/TableRenderers subpath.
| Key | Description |
|---|---|
Table |
Plain cross-tabulation table |
Table Heatmap |
Table with full color scale across all cells |
Table Col Heatmap |
Heatmap scaled per column |
Table Row Heatmap |
Heatmap scaled per row |
TsvExport |
Tab-separated values export view |
<script>
import { PivotTable, TableRenderers } from 'svelte-pivottable';
</script>
<PivotTable {data} rows={['region']} cols={['quarter']} renderers={TableRenderers} rendererName="Table Col Heatmap" />
Plotly.js is injected as a dependency to avoid mandatory bundling. Pass the Plotly object to PlotlyRenderers() to get the renderer map.
<script>
import { PivotTableUI, TableRenderers, PlotlyRenderers } from 'svelte-pivottable';
import Plotly from 'plotly.js';
const renderers = { ...TableRenderers, ...PlotlyRenderers(Plotly) };
</script>
<PivotTableUI {data} {renderers} />
If you load Plotly via a <script> tag rather than bundling it:
<script>
import { onMount } from 'svelte';
import { PivotTableUI, TableRenderers, PlotlyRenderers } from 'svelte-pivottable';
let renderers = { ...TableRenderers };
onMount(() => {
renderers = { ...TableRenderers, ...PlotlyRenderers(window.Plotly) };
});
</script>
<svelte:head>
<script src="https://cdn.plot.ly/plotly-basic-latest.min.js"></script>
</svelte:head>
<PivotTableUI {data} {renderers} />
The aggregators export is a ready-to-use map of aggregation functions. Pass it (or a subset) to PivotTableUI via the aggregators prop, or use individual entries as the aggregator prop on PivotTable.
<script>
import { PivotTable, aggregators } from 'svelte-pivottable';
</script>
<!-- Render a Sum table directly -->
<PivotTable
{data}
rows={['department']}
cols={['year']}
aggregator={aggregators['Sum']}
vals={['salary']}
/>
Available built-in aggregators:
| Key | Description |
|---|---|
Count |
Number of records |
Count Unique Values |
Number of distinct values |
List Unique Values |
Comma-separated distinct values |
Sum |
Sum of a numeric field |
Integer Sum |
Sum formatted as integer |
Average |
Mean |
Median |
Median |
Sample Variance |
Sample variance |
Sample Standard Deviation |
Sample standard deviation |
Minimum |
Minimum value |
Maximum |
Maximum value |
First |
First value seen |
Last |
Last value seen |
Sum over Sum |
Ratio of two sums |
Sum as Fraction of Total |
Each cell as % of grand total |
Sum as Fraction of Rows |
Each cell as % of its row total |
Sum as Fraction of Columns |
Each cell as % of its column total |
Count as Fraction of Total |
Count as % of grand total |
Count as Fraction of Rows |
Count as % of row total |
Count as Fraction of Columns |
Count as % of column total |
Pass a subset of aggregators to restrict what appears in the UI:
<script>
import { PivotTableUI, aggregators } from 'svelte-pivottable';
const myAggregators = {
Count: aggregators['Count'],
Sum: aggregators['Sum'],
Average: aggregators['Average'],
};
</script>
<PivotTableUI {data} aggregators={myAggregators} />
aggregatorTemplatesUse aggregatorTemplates as building blocks for your own aggregation logic:
<script>
import { PivotTableUI, aggregators, aggregatorTemplates, fmtInt } from 'svelte-pivottable';
// Count records where a field exceeds a threshold
const countAbove = (threshold) =>
([attr]) =>
() => ({
count: 0,
push(record) {
if (Number(record[attr]) > threshold) this.count++;
},
value() { return this.count; },
format: fmtInt,
numInputs: 1,
});
const myAggregators = {
...aggregators,
'High Earners': countAbove(100000),
};
</script>
<PivotTableUI {data} aggregators={myAggregators} aggregatorName="High Earners" vals={['salary']} />
Use derivedAttributes to compute new fields on the fly without modifying the source data. The built-in derivers helper provides common patterns:
<script>
import { PivotTableUI, derivers } from 'svelte-pivottable';
const data = [
{ name: 'Alice', born: '1990-06-15', salary: 95000 },
// ...
];
const derivedAttributes = {
// Bin a numeric field into buckets of 10
'Salary Band': derivers.bin('salary', 10000),
// Extract year from a date string
'Birth Year': derivers.dateFormat('born', '%y'),
// Fully custom computed field
'Senior': (record) => record.salary > 100000 ? 'Yes' : 'No',
};
</script>
<PivotTableUI {data} {derivedAttributes} rows={['Salary Band']} />
sortAsUse sortAs to enforce a fixed value ordering (e.g. month names, priority levels):
<script>
import { PivotTableUI, sortAs } from 'svelte-pivottable';
const sorters = {
quarter: sortAs(['Q1', 'Q2', 'Q3', 'Q4']),
priority: sortAs(['Low', 'Medium', 'High', 'Critical']),
};
</script>
<PivotTableUI {data} {sorters} cols={['quarter']} />
Set grouping={true} to display hierarchical row and column groups with subtotals at each level. This is the main feature added over the original react-pivottable.
<script>
import { PivotTableUI } from 'svelte-pivottable';
</script>
<PivotTableUI
{data}
rows={['continent', 'country', 'city']}
cols={['year', 'quarter']}
grouping={true}
rowGroupBefore={true}
colGroupBefore={false}
compactRows={true}
/>
| Prop | Type | Default | Description |
|---|---|---|---|
grouping |
boolean |
false |
Enable hierarchical grouping with subtotals |
compactRows |
boolean |
true |
Render row group headers inline rather than as separate rows |
rowGroupBefore |
boolean |
true |
Show row subtotals above their children |
colGroupBefore |
boolean |
false |
Show column subtotals to the left of their children |
The component stack passes props downward: PivotTableUI → PivotTable → Renderer → PivotData.
| Layer | Prop | Type | Default | Description |
|---|---|---|---|---|
PivotData |
data |
array or function | (required) | Dataset to summarize — see accepted formats |
PivotData |
rows |
string[] |
[] |
Attribute names to pre-populate in the row area |
PivotData |
cols |
string[] |
[] |
Attribute names to pre-populate in the column area |
PivotData |
vals |
string[] |
[] |
Attribute names passed to the aggregator function |
PivotData |
aggregator |
function | aggregators['Count'] |
Aggregation function (see aggregators) |
PivotData |
valueFilter |
Record<string, Record<string, boolean>> |
{} |
Values to exclude per attribute; populates the filter menus |
PivotData |
sorters |
object or function | {} |
Per-attribute sort functions; defaults to natural sort |
PivotData |
rowOrder |
string |
"key_a_to_z" |
Row sort order: key_a_to_z, value_a_to_z, or value_z_to_a |
PivotData |
colOrder |
string |
"key_a_to_z" |
Column sort order: key_a_to_z, value_a_to_z, or value_z_to_a |
PivotData |
derivedAttributes |
object of functions | {} |
Functions that add computed fields to each record |
PivotData |
grouping |
boolean |
false |
Enable subtotal rows and columns |
PivotData |
rowGroupBefore |
boolean |
true |
Place row subtotals above their group |
PivotData |
colGroupBefore |
boolean |
false |
Place column subtotals to the left of their group |
PivotTable |
renderers |
object | TableRenderers |
Map of renderer name → Svelte component |
PivotTable |
rendererName |
string |
first key in renderers |
Which renderer to use |
PivotTableUI |
aggregators |
object | aggregators from Utilities |
Map of aggregator name → function, shown in the dropdown |
PivotTableUI |
aggregatorName |
string |
first key in aggregators |
Which aggregator to use initially |
PivotTableUI |
hiddenAttributes |
string[] |
[] |
Attributes to hide from the entire UI |
PivotTableUI |
hiddenFromAggregators |
string[] |
[] |
Attributes to hide from the aggregator field picker |
PivotTableUI |
hiddenFromDragDrop |
string[] |
[] |
Attributes to hide from the drag-and-drop zone |
PivotTableUI |
menuLimit |
number |
500 |
Maximum number of values to show in a filter menu |
PivotTableUI |
unusedOrientationCutoff |
number |
85 |
Total character length of attribute names above which unused attributes are shown vertically instead of horizontally. 0 = always vertical, Infinity = always horizontal |
PivotTableUI |
compactRows |
boolean |
true |
Compact rendering for grouped row headers |
dataconst data = [
{ attr1: 'value1', attr2: 'value2' },
{ attr1: 'value3', attr2: 'value4' },
];
First row is treated as headers. Compatible with CSV parser output (e.g. PapaParse).
const data = [
['attr1', 'attr2'],
['value1', 'value2'],
['value3', 'value4'],
];
The function receives a record callback and calls it once per record. Useful for streaming or lazy data sources.
const data = (callback) => {
callback({ attr1: 'value1', attr2: 'value2' });
callback({ attr1: 'value3', attr2: 'value4' });
};
Missing attributes and null values are treated as the string "null" in all formats.
PivotTableUI — stateful drag-and-drop UI
└─ PivotTable — stateless renderer selector
└─ Renderer — TableRenderer, PlotlyRenderer, TSVExportRenderer, etc.
└─ PivotData — aggregation, filtering, sorting, subtotal tree
MIT