Row Grouping

Groups records by a value.

Orders by region — grouped by Region
OrderCustomerStatusTotal
EMEA (3)
#1042A. WhitfieldShipped£64.00
#1041R. OkaforProcessing£128.50
#1038L. DuarteShipped£96.20
Americas (2)
#1040M. IqbalShipped£22.00
#1039S. NovakRefunded£40.00

html

<div class="rgrp" style="--accent:#241430;">
  <div class="rgrp__scroll">
    <table class="rgrp__table">
      <caption>Orders by region — grouped by Region</caption>
      <thead><tr><th scope="col">Order</th><th scope="col">Customer</th><th scope="col">Status</th><th scope="col">Total</th></tr></thead>
      <tbody>
        <tr class="rgrp__group"><td colspan="4">EMEA <span class="rgrp__count">(3)</span></td></tr>
        
        <tr><td>#1042</td><td>A. Whitfield</td><td>Shipped</td><td>£64.00</td></tr>
        <tr><td>#1041</td><td>R. Okafor</td><td>Processing</td><td>£128.50</td></tr>
        <tr><td>#1038</td><td>L. Duarte</td><td>Shipped</td><td>£96.20</td></tr>
        
        <tr class="rgrp__group"><td colspan="4">Americas <span class="rgrp__count">(2)</span></td></tr>
        <tr><td>#1040</td><td>M. Iqbal</td><td>Shipped</td><td>£22.00</td></tr>
        <tr><td>#1039</td><td>S. Novak</td><td>Refunded</td><td>£40.00</td></tr>
      </tbody>
    </table>
  </div>
</div>
<style>
.rgrp { font: 14px system-ui, sans-serif; color: #241430; background: #FAF7F2; max-width: 480px; border: 1px solid #24143020; border-radius: 10px; overflow: hidden; }
.rgrp__scroll { overflow-x: auto; }
.rgrp__table { border-collapse: collapse; width: 100%; }
.rgrp__table caption { text-align: left; font-weight: 600; padding: 10px 14px; }
.rgrp__table th, .rgrp__table td { padding: 9px 14px; text-align: left; border-bottom: 1px solid #24143020; white-space: nowrap; }
.rgrp__table th { background: var(--accent); color: #FAF7F2; }
.rgrp__group td { background: #24143012; font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
.rgrp__count { font-weight: 400; text-transform: none; color: #241430a0; }
</style>

tailwind

<div class="max-w-[480px] rounded-[10px] border border-[#24143020] font-sans text-sm text-[#241430] bg-[#FAF7F2] overflow-hidden" style="--accent:#241430;">
  <div class="overflow-x-auto">
    <table class="border-collapse w-full">
      <caption class="text-left font-semibold px-3.5 py-2.5">Orders by region — grouped by Region</caption>
      <thead><tr class="text-[#FAF7F2]" style="background:#241430">
        <th scope="col" class="p-2.5 text-left whitespace-nowrap">Order</th>
        <th scope="col" class="p-2.5 text-left whitespace-nowrap">Customer</th>
        <th scope="col" class="p-2.5 text-left whitespace-nowrap">Status</th>
        <th scope="col" class="p-2.5 text-left whitespace-nowrap">Total</th>
      </tr></thead>
      <tbody>
        <tr><td colspan="4" class="bg-[#24143012] font-semibold text-xs uppercase tracking-wide p-2.5">EMEA <span class="font-normal normal-case text-[#241430a0]">(3)</span></td></tr>
        
        <tr><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">#1042</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">A. Whitfield</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">Shipped</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">£64.00</td></tr>
        <tr><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">#1041</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">R. Okafor</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">Processing</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">£128.50</td></tr>
        <tr><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">#1038</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">L. Duarte</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">Shipped</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">£96.20</td></tr>
        
        <tr><td colspan="4" class="bg-[#24143012] font-semibold text-xs uppercase tracking-wide p-2.5">Americas <span class="font-normal normal-case text-[#241430a0]">(2)</span></td></tr>
        <tr><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">#1040</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">M. Iqbal</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">Shipped</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">£22.00</td></tr>
        <tr><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">#1039</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">S. Novak</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">Refunded</td><td class="p-2.5 border-b border-[#24143020] whitespace-nowrap">£40.00</td></tr>
      </tbody>
    </table>
  </div>
</div>

react

function RowGrouping({
  caption = "Orders by region",
  accent = "#241430",
  groupBy = "Region",
  collapsedGroup = false,
  showCounts = true,
}) {
  const cell = { padding: "9px 14px", borderBottom: "1px solid #24143020", whiteSpace: "nowrap" };
  const groupRow = { background: "#24143012", fontWeight: 600, fontSize: 12, textTransform: "uppercase", letterSpacing: ".04em", padding: "9px 14px" };
  const groups = [
    { name: "EMEA", collapsible: true, rows: [["#1042", "A. Whitfield", "Shipped", "£64.00"], ["#1041", "R. Okafor", "Processing", "£128.50"], ["#1038", "L. Duarte", "Shipped", "£96.20"]] },
    { name: "Americas", collapsible: false, rows: [["#1040", "M. Iqbal", "Shipped", "£22.00"], ["#1039", "S. Novak", "Refunded", "£40.00"]] },
  ];
  return (
    <div style={{ maxWidth: 480, borderRadius: 10, border: "1px solid #24143020", overflow: "hidden", font: "14px system-ui, sans-serif", color: "#241430", background: "#FAF7F2" }}>
      <div style={{ overflowX: "auto" }}>
        <table style={{ borderCollapse: "collapse", width: "100%" }}>
          <caption style={{ textAlign: "left", fontWeight: 600, padding: "10px 14px" }}>{caption} — grouped by {groupBy}</caption>
          <thead>
            <tr style={{ background: accent, color: "#FAF7F2" }}>
              {["Order", "Customer", "Status", "Total"].map((h) => <th key={h} scope="col" style={cell}>{h}</th>)}
            </tr>
          </thead>
          <tbody>
            {groups.flatMap((g) => {
              const header = (
                <tr key={`${g.name}-header`}><td colSpan={4} style={groupRow}>{g.name}{showCounts && <span style={{ fontWeight: 400, textTransform: "none", color: "#241430a0" }}> ({g.rows.length})</span>}</td></tr>
              );
              const dataRows = (g.collapsible && collapsedGroup) ? [] : g.rows.map((row) => (
                <tr key={row[0]}>{row.map((c, j) => <td key={j} style={cell}>{c}</td>)}</tr>
              ));
              return [header, ...dataRows];
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}