Server-side Sorting

Sorting performed by the backend.

Customer orders · 180ms
OrderCustomerStatus
#1042A. WhitfieldShipped£22.00
#1041R. OkaforProcessing£40.00
#1040M. IqbalShipped£64.00
#1039S. NovakRefunded£128.50

html

<div class="sss" style="--accent:#241430;">
  <div class="sss__scroll">
    <table class="sss__table">
      <caption>Customer orders <span class="sss__status" role="status">&#183; 180ms</span></caption>
      <thead><tr>
        <th scope="col">Order</th><th scope="col">Customer</th><th scope="col">Status</th>
        <th scope="col" aria-sort="ascending"><button type="button" class="sss__sortbtn">Total <span class="sss__arrow" aria-hidden="true">&#9650;</span></button></th>
      </tr></thead>
      <tbody>
        <tr><td>#1042</td><td>A. Whitfield</td><td>Shipped</td><td>£22.00</td></tr>
        <tr><td>#1041</td><td>R. Okafor</td><td>Processing</td><td>£40.00</td></tr>
        <tr><td>#1040</td><td>M. Iqbal</td><td>Shipped</td><td>£64.00</td></tr>
        <tr><td>#1039</td><td>S. Novak</td><td>Refunded</td><td>£128.50</td></tr>
      </tbody>
    </table>
  </div>
</div>
<style>
.sss { font: 14px system-ui, sans-serif; color: #241430; background: #FAF7F2; max-width: 480px; border: 1px solid #24143020; border-radius: 10px; overflow: hidden; }
.sss__scroll { overflow-x: auto; }
.sss--loading .sss__scroll { opacity: .55; }
.sss__table { border-collapse: collapse; width: 100%; }
.sss__table caption { text-align: left; font-weight: 600; padding: 10px 14px; }
.sss__status { font-weight: 400; font-size: 12px; color: #241430a0; margin-left: 6px; display: inline-flex; align-items: center; gap: 6px; }
.sss__spinner { width: 10px; height: 10px; border-radius: 50%; border: 2px solid #24143030; border-top-color: var(--accent); }
@keyframes sss-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: no-preference) {
  .sss__scroll { transition: opacity .15s; }
  .sss__spinner { animation: sss-spin .2s linear infinite; }
}
.sss__table th, .sss__table td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #24143020; white-space: nowrap; }
.sss__table th { background: var(--accent); color: #FAF7F2; }
.sss__sortbtn { background: none; border: 0; font: inherit; color: inherit; cursor: pointer; display: inline-flex; gap: 4px; align-items: center; padding: 0; }
.sss__sortbtn:hover { text-decoration: underline; }
.sss__sortbtn:focus-visible { outline: 2px solid #FAF7F2; outline-offset: 2px; }
.sss[aria-sort] { }
th[aria-sort="descending"] .sss__arrow { transform: rotate(180deg); display: inline-block; }
</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 motion-safe:transition-opacity">
    <table class="border-collapse w-full">
      <caption class="text-left font-semibold px-3.5 py-2.5">Customer orders <span role="status" class="font-normal text-xs text-[#241430a0] ml-1.5 inline-flex items-center gap-1.5">&#183; 180ms</span></caption>
      <thead><tr class="text-[#FAF7F2]" style="background:#241430">
        <th scope="col" class="p-3.5 text-left whitespace-nowrap">Order</th>
        <th scope="col" class="p-3.5 text-left whitespace-nowrap">Customer</th>
        <th scope="col" class="p-3.5 text-left whitespace-nowrap">Status</th>
        <th scope="col" class="sss__th p-3.5 text-left whitespace-nowrap" aria-sort="ascending"><button type="button" class="bg-transparent border-0 font-inherit text-inherit cursor-pointer inline-flex gap-1 items-center p-0 hover:underline">Total <span aria-hidden="true" class="sss__arrow inline-block">&#9650;</span></button></th>
      </tr></thead>
      <tbody>
        <tr><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">#1042</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">A. Whitfield</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">Shipped</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">£22.00</td></tr>
        <tr><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">#1041</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">R. Okafor</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">Processing</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">£40.00</td></tr>
        <tr><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">#1040</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">M. Iqbal</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">Shipped</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">£64.00</td></tr>
        <tr><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">#1039</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">S. Novak</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">Refunded</td><td class="p-3.5 border-b border-[#24143020] whitespace-nowrap">£128.50</td></tr>
      </tbody>
    </table>
  </div>
</div>
<style>
th[aria-sort="descending"] .sss__arrow { transform: rotate(180deg); display: inline-block; }
</style>

react

function ServerSideSorting({
  caption = "Customer orders",
  accent = "#241430",
  direction = "ascending",
  loading = false,
  latency = 180,
}) {
  const rows = [
    ["#1042", "A. Whitfield", "Shipped", "£22.00"],
    ["#1041", "R. Okafor", "Processing", "£40.00"],
    ["#1040", "M. Iqbal", "Shipped", "£64.00"],
    ["#1039", "S. Novak", "Refunded", "£128.50"],
  ];
  const cell = { padding: "10px 14px", borderBottom: "1px solid #24143020", whiteSpace: "nowrap" };
  return (
    <div style={{ maxWidth: 480, borderRadius: 10, border: "1px solid #24143020", overflow: "hidden", font: "14px system-ui, sans-serif", color: "#241430", background: "#FAF7F2" }}>
      <div className="sss-table-wrap" style={{ overflowX: "auto", opacity: loading ? .55 : 1 }}>
        <table style={{ borderCollapse: "collapse", width: "100%" }}>
          <caption style={{ textAlign: "left", fontWeight: 600, padding: "10px 14px" }}>
            {caption}{" "}
            <span role="status" style={{ fontWeight: 400, fontSize: 12, color: "#241430a0", marginLeft: 6, display: "inline-flex", alignItems: "center", gap: 6 }}>
              {loading ? <>Sorting&#8230;</> : <>&#183; {latency}ms</>}
            </span>
          </caption>
          <thead>
            <tr style={{ background: accent, color: "#FAF7F2" }}>
              <th scope="col" style={cell}>Order</th>
              <th scope="col" style={cell}>Customer</th>
              <th scope="col" style={cell}>Status</th>
              <th scope="col" style={cell} aria-sort={direction}>
                <button type="button" style={{ background: "none", border: 0, font: "inherit", color: "inherit", cursor: "pointer", display: "inline-flex", gap: 4, alignItems: "center", padding: 0 }}>
                  Total <span aria-hidden="true" style={{ display: "inline-block", transform: direction === "descending" ? "rotate(180deg)" : "none" }}>{direction !== "none" && "▲"}</span>
                </button>
              </th>
            </tr>
          </thead>
          <tbody>
            {rows.map((row) => (
              <tr key={row[0]}>{row.map((c, j) => <td key={j} style={cell}>{c}</td>)}</tr>
            ))}
          </tbody>
        </table>
      </div>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          .sss-table-wrap { transition: opacity .15s; }
        }
      `}</style>
    </div>
  );
}