webdevpuneet.com

Pivot Table — Cross-Tab HTML CSS JS (No Library)

Pivot Table · Tables · Plain HTML, CSS & JS · Live preview

CategoryTables

Copy & paste

Source Code

<div class="pv-wrap">
  <div class="pv-bar">
    <h3>Sales pivot</h3>
    <label class="pv-agg">Measure
      <select id="pvAgg"><option value="sum">Sum</option><option value="avg">Average</option><option value="count">Count</option></select>
    </label>
  </div>
  <div class="pv-scroll"><table class="pv-table" id="pvTable"></table></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Cross-tabulation
Reshapes flat records into a row × column summary matrix.
Switchable measures
Sum, average, or count — all through one aggregate function for consistency.
Row and column totals
A Total column and Total row summarise each axis.
Correct non-additive totals
Totals aggregate the raw records, so averages stay correct (not an average of averages).
Grand total
The corner of the totals aggregates the whole dataset.
Aligned numeric grid
Right-aligned tabular cells and left-aligned headers keep columns readable.
Horizontal scroll
The table scrolls when there are many columns.
Data-driven & no library
Pivots a flat DATA array by configurable fields in plain HTML/CSS/JS.

About this UI Snippet

Pivot Table — Cross-Tabulate Flat Records by Row and Column with Totals

Screenshot of the Pivot Table snippet rendered live

A pivot table turns a flat list of records into a cross-tabulation: pick a field for the rows, a field for the columns, and a measure for the cells, and it summarises the data into a grid with totals — the core of every spreadsheet pivot and BI tool. This snippet builds a working pivot table in plain HTML, CSS, and vanilla JavaScript, aggregating the data itself with switchable sum/average/count measures and full totals — no library.

Cross-tabulation from flat data

The input is a flat array of records (region, quarter, sales). The pivot derives the unique row values and unique column values, then for each row/column intersection filters the records that match both and aggregates them into a cell. This filter-and-aggregate at every intersection is the essence of a pivot — it reshapes one-row-per-event data into a summary matrix, which is exactly what makes raw transactional data readable.

Switchable measures

A selector changes the aggregation across the whole table — sum, average, or count — and re-pivots live. aggregate(records, how) is the single function that defines each measure: count is the record tally, sum totals the value field, average divides the two. Because every cell, row total, column total, and the grand total all route through this one function, switching the measure recomputes the entire grid consistently — there's no chance of the totals using a different calculation than the cells.

Totals on both axes

The table adds a Total column (each row aggregated across all columns), a Total row in the footer (each column aggregated across all rows), and a grand total at their intersection. Crucially, totals are computed from the *raw records*, not by summing the displayed cells — this matters for the average measure, where averaging the cell averages would be wrong; aggregating the underlying records gives the correct overall average. Getting totals right under non-additive measures is the detail that separates a real pivot from a naive grid.

Readable, scrollable layout

Row headers and the corner are left-aligned while numeric cells are right-aligned with tabular figures so columns line up, totals are emphasised with a tinted background, and the table scrolls horizontally if there are many columns. These are the conventions that make a dense numeric grid scannable.

Data-driven and drop-in

Change the ROW, COL, and VAL field names at the top to pivot any flat dataset by different dimensions, or wire those to selectors for a fully interactive pivot. It's a clear, dependency-free reference for the cross-tabulation and aggregation logic behind every pivot table. The filter-per-cell approach here is intentionally simple and easy to follow for a dataset this size; it scans the full DATA array once per intersection, so it's O(rows × cols × records) — fine for dozens of cells, but for a pivot over thousands of records you'd first group records once into a Map keyed by row+col, then read each cell's aggregate from that map in O(1). The same regrouping approach extends naturally to a pivot with more than two dimensions — a third "page" field, say — by keying the map on all three values and adding a selector that filters which page's slice is currently rendered, without changing how the row/column grid itself is built.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA pivot table renders, cross-tabulating sales by region (rows) and quarter (columns).
  2. 2
    Switch the measureUse the Measure selector to re-pivot as sum, average, or count.
  3. 3
    Read the totalsA Total column, Total row, and grand total summarise both axes.
  4. 4
    Change the dimensionsEdit the ROW, COL, and VAL field names to pivot by different fields.
  5. 5
    Swap in your dataReplace the flat DATA array with your own records.
  6. 6
    Make it interactiveWire ROW/COL/VAL to selectors to let users choose the pivot dimensions.

Real-world uses

Common Use Cases

Sales by region and product
Cross-tabulate flat records into a row by column grid, summarising revenue with row, column and grand totals.
Analytics breakdowns
Summarise events by two dimensions, pairing with a bar chart so the same figures can be viewed as a picture.
Finance and budgeting
Pivot spend by category and month using sum, average or count, with totals aggregated from raw records so averages remain correct.
Survey cross-tabs
Cross-tabulate survey responses by two questions at once, switching between sum, average and count through a single aggregate function.
Inventory across locations
Summarise stock by site and type, or compare with a flat data table and a grouped rows table for different levels of detail.
Related: Table Column Pin/Unpin Toggle — User-Controlled Sticky Columns
See the Table Column Pin/Unpin Toggle — User-Controlled Sticky Columns for a related tables pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

A normal table shows one row per record. A pivot table cross-tabulates: it picks a field for the rows and a field for the columns, then summarises a measure at each intersection. This reshapes flat, one-row-per-event data into a compact summary grid — turning, say, twelve sales records into a region-by-quarter matrix with totals.

For additive measures like sum or count, summing the cells happens to give the right total. But for average, averaging the displayed cell-averages is mathematically wrong — it ignores how many records each cell represents. Aggregating the underlying raw records for every total guarantees correctness across all measures, which is essential for a trustworthy pivot.

Change the ROW, COL, and VAL constants to the field names you want as rows, columns, and the measured value. The code derives the unique values and aggregates generically, so any flat dataset pivots by any of its fields. To make it interactive, bind those constants to dropdowns and call render() on change.

Yes — the grid is generated from the unique values in your data, so it scales to any number of rows and columns, and the container scrolls horizontally when columns overflow. For very large datasets you'd precompute the aggregation once (e.g. group records into a map keyed by row+column) instead of filtering per cell, but the logic is the same.

In React, hold the data and measure in state and compute the pivoted structure with useMemo, then render the grid from it; in Vue, use a computed pivot object with v-for; in Angular, a getter with *ngFor. The uniq()/aggregate() cross-tab logic is framework-agnostic — only the measure state and rendering move into the framework.