Code Block
Code to read and copy: file name, line numbers, highlighted lines and a monochrome syntax in weight and light. Copy rolls to Copied; a terminal copies its commands only.
View codeA data table that sorts when you click a column: rows glide to their new place, the sorted column gets a soft band, the header stays on top and the edges fade while it scrolls.
With your AI
Say it to your AI. It gets the exact code below, not its best guess, and knows where it goes.
Show my orders in a sortable Kodza Table.
claude mcp add -t http kodza https://kodza.dev/api/mcp
<div class="kodza-table" role="region" aria-label="Most copied components" tabindex="0">
<table>
<thead>
<tr>
<th scope="col"><button class="kodza-table__sort" type="button">Component<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M6 2.5v7M3 6.5l3 3 3-3" /></svg></button></th>
<th scope="col"><button class="kodza-table__sort" type="button">Category<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M6 2.5v7M3 6.5l3 3 3-3" /></svg></button></th>
<th scope="col" class="kodza-table__num"><button class="kodza-table__sort" type="button">Size<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M6 2.5v7M3 6.5l3 3 3-3" /></svg></button></th>
<th scope="col" class="kodza-table__num" aria-sort="descending"><button class="kodza-table__sort" type="button">Copies<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M6 2.5v7M3 6.5l3 3 3-3" /></svg></button></th>
<th scope="col" class="kodza-table__num"><button class="kodza-table__sort" type="button">Updated<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M6 2.5v7M3 6.5l3 3 3-3" /></svg></button></th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="kodza-table__name">Fill Button</span><span class="kodza-table__slug">button-fill</span></td>
<td>Buttons</td>
<td class="kodza-table__num" data-sort="2.1">2.1 KB</td>
<td class="kodza-table__num" data-sort="12480">12,480</td>
<td class="kodza-table__num" data-sort="2026-10-02"><time datetime="2026-10-02">Oct 2</time></td>
<td><span class="kodza-table__status" data-status="stable">Stable</span></td>
</tr>
<tr>
<td><span class="kodza-table__name">Toast</span><span class="kodza-table__slug">toast-notification</span></td>
<td>Overlays</td>
<td class="kodza-table__num" data-sort="6.8">6.8 KB</td>
<td class="kodza-table__num" data-sort="9312">9,312</td>
<td class="kodza-table__num" data-sort="2026-10-16"><time datetime="2026-10-16">Oct 16</time></td>
<td><span class="kodza-table__status" data-status="stable">Stable</span></td>
</tr>
<tr>
<td><span class="kodza-table__name">Command Palette</span><span class="kodza-table__slug">command-palette</span></td>
<td>Navigation</td>
<td class="kodza-table__num" data-sort="9.4">9.4 KB</td>
<td class="kodza-table__num" data-sort="7045">7,045</td>
<td class="kodza-table__num" data-sort="2026-10-14"><time datetime="2026-10-14">Oct 14</time></td>
<td><span class="kodza-table__status" data-status="beta">Beta</span></td>
</tr>
<tr>
<td><span class="kodza-table__name">Skeleton</span><span class="kodza-table__slug">skeleton-loader</span></td>
<td>Feedback</td>
<td class="kodza-table__num" data-sort="4.2">4.2 KB</td>
<td class="kodza-table__num" data-sort="5980">5,980</td>
<td class="kodza-table__num" data-sort="2026-10-16"><time datetime="2026-10-16">Oct 16</time></td>
<td><span class="kodza-table__status" data-status="new">New</span></td>
</tr>
<tr>
<td><span class="kodza-table__name">Image Gallery</span><span class="kodza-table__slug">image-gallery</span></td>
<td>Images</td>
<td class="kodza-table__num" data-sort="7.7">7.7 KB</td>
<td class="kodza-table__num" data-sort="5214">5,214</td>
<td class="kodza-table__num" data-sort="2026-09-28"><time datetime="2026-09-28">Sep 28</time></td>
<td><span class="kodza-table__status" data-status="stable">Stable</span></td>
</tr>
<tr>
<td><span class="kodza-table__name">Date Picker</span><span class="kodza-table__slug">date-picker</span></td>
<td>Forms</td>
<td class="kodza-table__num" data-sort="11.2">11.2 KB</td>
<td class="kodza-table__num" data-sort="4871">4,871</td>
<td class="kodza-table__num" data-sort="2026-10-12"><time datetime="2026-10-12">Oct 12</time></td>
<td><span class="kodza-table__status" data-status="beta">Beta</span></td>
</tr>
<tr>
<td><span class="kodza-table__name">Marquee</span><span class="kodza-table__slug">scrolling-text-loop</span></td>
<td>Text</td>
<td class="kodza-table__num" data-sort="1.3">1.3 KB</td>
<td class="kodza-table__num" data-sort="3902">3,902</td>
<td class="kodza-table__num" data-sort="2026-08-30"><time datetime="2026-08-30">Aug 30</time></td>
<td><span class="kodza-table__status" data-status="stable">Stable</span></td>
</tr>
<tr>
<td><span class="kodza-table__name">Alert Dialog</span><span class="kodza-table__slug">alert-dialog</span></td>
<td>Overlays</td>
<td class="kodza-table__num" data-sort="3.9">3.9 KB</td>
<td class="kodza-table__num" data-sort="1204">1,204</td>
<td class="kodza-table__num" data-sort="2026-03-03"><time datetime="2026-03-03">Mar 3</time></td>
<td><span class="kodza-table__status" data-status="deprecated">Deprecated</span></td>
</tr>
</tbody>
</table>
</div>
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — Table
A data table that sorts when you click a column title: the rows glide to their new place,
the sorted column gets a soft band and an arrow that turns with the order. Numbers are
right-aligned in tabular figures. The header stays put when the table scrolls
(--kz-table-max sets a height), and the edges fade while there is more to scroll sideways.
A status is told by a shape: a full dot, a ring, an ink pill, or struck through.
Everything is tuned with the --kz-table-* variables. */
.kodza-table {
--kz-table-bg: #ffffff;
--kz-table-fg: #0a0a0a;
--kz-table-muted: rgba(10, 10, 10, 0.6);
--kz-table-line: rgba(10, 10, 10, 0.09);
--kz-table-border: rgba(10, 10, 10, 0.1);
--kz-table-soft: rgba(10, 10, 10, 0.035);
--kz-table-ink: #0a0a0a;
--kz-table-on-ink: #ffffff;
--kz-table-max: none;
--kz-table-fade-l: 0px;
--kz-table-fade-r: 0px;
--kz-table-ease: cubic-bezier(0.22, 1, 0.36, 1);
max-height: var(--kz-table-max);
overflow: auto;
border-radius: 16px;
background: var(--kz-table-bg);
box-shadow: inset 0 0 0 1px var(--kz-table-border);
color: var(--kz-table-fg);
font-size: 14px;
-webkit-mask: linear-gradient(to right, transparent, #000 var(--kz-table-fade-l), #000 calc(100% - var(--kz-table-fade-r)), transparent);
mask: linear-gradient(to right, transparent, #000 var(--kz-table-fade-l), #000 calc(100% - var(--kz-table-fade-r)), transparent);
}
.kodza-table:focus-visible {
outline: 2px solid var(--kz-table-fg);
outline-offset: 3px;
}
.kodza-table table {
width: 100%;
border-collapse: collapse;
white-space: nowrap;
}
.kodza-table th,
.kodza-table td {
height: 52px;
padding: 0 18px;
text-align: left;
}
.kodza-table th:first-child,
.kodza-table td:first-child {
padding-left: 24px;
}
.kodza-table th:last-child,
.kodza-table td:last-child {
padding-right: 24px;
}
/* The header: stays on top when the table scrolls */
.kodza-table thead th {
position: sticky;
top: 0;
z-index: 1;
height: 46px;
background: var(--kz-table-bg);
box-shadow: inset 0 -1px 0 var(--kz-table-border);
color: var(--kz-table-muted);
font-size: 13px;
font-weight: 500;
}
.kodza-table__sort {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 0 -8px;
padding: 6px 8px;
border: 0;
border-radius: 8px;
background: none;
color: inherit;
font: inherit;
cursor: pointer;
transition: color 0.2s ease, background-color 0.2s ease;
}
.kodza-table__sort:hover {
background: var(--kz-table-soft);
color: var(--kz-table-fg);
}
.kodza-table__sort:focus-visible {
outline: 2px solid var(--kz-table-fg);
outline-offset: 1px;
}
.kodza-table__sort svg {
width: 12px;
height: 12px;
fill: none;
stroke: currentColor;
stroke-width: 1.6;
stroke-linecap: round;
stroke-linejoin: round;
opacity: 0;
transform: translateY(-2px);
transition: opacity 0.2s ease, transform 0.45s var(--kz-table-ease);
}
.kodza-table__sort:hover svg {
opacity: 0.4;
transform: none;
}
.kodza-table th[aria-sort] {
color: var(--kz-table-fg);
}
.kodza-table th[aria-sort] svg {
opacity: 1;
transform: none;
}
.kodza-table th[aria-sort="ascending"] svg {
transform: rotate(180deg);
}
/* The sorted column: a soft band */
.kodza-table td.is-sorted {
background: var(--kz-table-soft);
}
.kodza-table th.kodza-table__num,
.kodza-table td.kodza-table__num {
text-align: right;
font-variant-numeric: tabular-nums;
}
.kodza-table th.kodza-table__num .kodza-table__sort {
flex-direction: row-reverse;
}
/* Rows have a body: two rows crossing while they glide never mix their words */
.kodza-table tbody tr {
position: relative;
background: var(--kz-table-bg);
}
.kodza-table tbody td {
transition: background-color 0.2s ease;
}
.kodza-table tbody tr + tr td {
border-top: 1px solid var(--kz-table-line);
}
.kodza-table tbody tr:hover td {
background-image: linear-gradient(var(--kz-table-soft), var(--kz-table-soft));
}
.kodza-table__name {
display: block;
font-weight: 500;
}
.kodza-table__slug {
display: block;
margin-top: 1px;
color: var(--kz-table-muted);
font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
font-size: 11.5px;
}
.kodza-table tbody td:has(.kodza-table__name) {
height: 60px;
}
/* Status: a shape says it, not a color */
.kodza-table__status {
display: inline-flex;
align-items: center;
gap: 8px;
}
.kodza-table__status::before {
content: "";
width: 7px;
height: 7px;
box-sizing: border-box;
border-radius: 50%;
background: var(--kz-table-ink);
}
.kodza-table__status[data-status="beta"]::before {
background: none;
border: 1.5px solid var(--kz-table-ink);
}
.kodza-table__status[data-status="new"] {
padding: 3px 8px;
border-radius: 999px;
background: var(--kz-table-ink);
color: var(--kz-table-on-ink);
font-size: 12px;
font-weight: 500;
}
.kodza-table__status[data-status="new"]::before {
display: none;
}
.kodza-table__status[data-status="deprecated"] {
color: var(--kz-table-muted);
text-decoration: line-through;
}
.kodza-table__status[data-status="deprecated"]::before {
width: 8px;
height: 1.5px;
border-radius: 1px;
background: var(--kz-table-muted);
}
@media (prefers-reduced-motion: reduce) {
.kodza-table__sort,
.kodza-table__sort svg,
.kodza-table tbody td {
transition-duration: 0.01ms !important;
}
}
/* Theming example — on a dark background:
.dark-section .kodza-table {
--kz-table-bg: #161616;
--kz-table-fg: #f2f2f0;
--kz-table-muted: rgba(242, 242, 240, 0.6);
--kz-table-line: rgba(255, 255, 255, 0.08);
--kz-table-border: rgba(255, 255, 255, 0.12);
--kz-table-soft: rgba(255, 255, 255, 0.04);
--kz-table-ink: #f2f2f0;
--kz-table-on-ink: #0a0a0a;
}
*/
/* kodza-guard 866cc02513fd — generated by Kodza, do not edit.
Theme guard: keeps a site's own CSS (WordPress themes, Bootstrap, CSS resets…) off this
component. :not(#_) is always true and weighs as much as an id, so these lines win over
theme rules made of classes and tags. Customise the component through its --kz-* variables. */
/* What the browser gives, where the component sets it only in some states: at the lowest weight, so its own rules below put it back */
:where(.kodza-table):not(#_),
:where(.kodza-table__sort):not(#_) {
outline: revert;
outline-offset: revert;
}
:where(.kodza-table__num):not(#_) {
transition: revert;
position: revert;
border-top-width: revert;
border-top-style: revert;
border-top-color: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
color: revert;
font-size: revert;
font-weight: revert;
}
:where(.kodza-table tr:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *))):not(#_) {
position: revert;
background-color: revert;
background-image: revert;
}
:where(.kodza-table td:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *))):not(#_) {
border-top-width: revert;
border-top-style: revert;
border-top-color: revert;
background-color: revert;
background-image: revert;
}
/* What the browser gives, on everything the component leaves to it */
.kodza-table:not(#_),
.kodza-table__name:not(#_),
.kodza-table__slug:not(#_),
.kodza-table__status:not(#_) {
box-sizing: revert;
}
.kodza-table__sort:not(#_) {
text-decoration: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
white-space: revert;
min-height: revert;
}
.kodza-table__num:not(#_) {
margin: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
list-style: revert;
box-sizing: revert;
float: revert;
border-right-width: revert;
border-bottom-width: revert;
border-left-width: revert;
border-right-style: revert;
border-bottom-style: revert;
border-left-style: revert;
border-right-color: revert;
border-bottom-color: revert;
border-left-color: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-table table:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_),
.kodza-table thead:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_),
.kodza-table tbody:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_),
.kodza-table__num time:where(:not([class*="kodza-"])):not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-table tr:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-table th:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_) {
border: revert;
margin: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-family: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-table__sort svg:where(:not([class*="kodza-"])):not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-table td:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_) {
margin: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
border-right-width: revert;
border-bottom-width: revert;
border-left-width: revert;
border-right-style: revert;
border-bottom-style: revert;
border-left-style: revert;
border-right-color: revert;
border-bottom-color: revert;
border-left-color: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-table__num:not(#_):where(:not([hidden])),
.kodza-table table:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-table thead:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-table tr:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-table th:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-table__sort svg:where(:not([class*="kodza-"])):not(#_):where(:not([hidden])),
.kodza-table tbody:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-table td:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where(:not([hidden])),
.kodza-table__num time:where(:not([class*="kodza-"])):not(#_):where(:not([hidden])) {
display: revert;
}
.kodza-table__num:not(#_):where([hidden]),
.kodza-table table:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-table thead:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-table tr:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-table th:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-table__sort svg:where(:not([class*="kodza-"])):not(#_):where([hidden]),
.kodza-table tbody:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-table td:where(:not([class*="kodza-"]):not(.kodza-table [class*="kodza-"] *)):not(#_):where([hidden]),
.kodza-table__num time:where(:not([class*="kodza-"])):not(#_):where([hidden]) {
display: none;
}
/* The component's own rules, one step stronger: real buttons and fields, what it sets on tags themes style (links, lists, headings, images…), and the root width (a theme may narrow it, never widen it) */
.kodza-table:not(#_) {
box-shadow: inset 0 0 0 1px var(--kz-table-border);
}
.kodza-table:focus-visible:not(#_) {
outline: 2px solid var(--kz-table-fg);
outline-offset: 3px;
}
.kodza-table th:not(#_),
.kodza-table td:not(#_) {
padding: 0 18px;
text-align: left;
}
.kodza-table th:first-child:not(#_),
.kodza-table td:first-child:not(#_) {
padding-left: 24px;
}
.kodza-table th:last-child:not(#_),
.kodza-table td:last-child:not(#_) {
padding-right: 24px;
}
.kodza-table thead th:not(#_) {
position: sticky;
background: var(--kz-table-bg);
box-shadow: inset 0 -1px 0 var(--kz-table-border);
color: var(--kz-table-muted);
font-size: 13px;
font-weight: 500;
}
.kodza-table__sort:not(#_) {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 0 -8px;
padding: 6px 8px;
border: 0;
border-radius: 8px;
background: none;
color: inherit;
font: inherit;
cursor: pointer;
transition: color 0.2s ease, background-color 0.2s ease;
}
.kodza-table__sort:hover:not(#_) {
background: var(--kz-table-soft);
color: var(--kz-table-fg);
}
.kodza-table__sort:focus-visible:not(#_) {
outline: 2px solid var(--kz-table-fg);
outline-offset: 1px;
}
.kodza-table__sort svg:not(#_) {
transition: opacity 0.2s ease, transform 0.45s var(--kz-table-ease);
}
.kodza-table th[aria-sort]:not(#_) {
color: var(--kz-table-fg);
}
.kodza-table td.is-sorted:not(#_) {
background: var(--kz-table-soft);
}
.kodza-table th.kodza-table__num:not(#_),
.kodza-table td.kodza-table__num:not(#_) {
text-align: right;
}
.kodza-table th.kodza-table__num .kodza-table__sort:not(#_) {
flex-direction: row-reverse;
}
.kodza-table tbody tr:not(#_) {
position: relative;
background: var(--kz-table-bg);
}
.kodza-table tbody td:not(#_) {
transition: background-color 0.2s ease;
}
.kodza-table tbody tr + tr td:not(#_) {
border-top: 1px solid var(--kz-table-line);
}
.kodza-table tbody tr:hover td:not(#_) {
background-image: linear-gradient(var(--kz-table-soft), var(--kz-table-soft));
}
@media (prefers-reduced-motion: reduce) {
.kodza-table__sort:not(#_) {
transition-duration: 0.01ms !important;
}
}
@media (prefers-reduced-motion: reduce) {
.kodza-table__sort svg:not(#_),
.kodza-table tbody td:not(#_) {
transition-duration: 0.01ms !important;
}
}
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Kodza — Table
// Sorts by the clicked column (numbers and dates by their data-sort value, words by their
// text), keeps aria-sort in step and glides each row from its old place to its new one. Also
// fades the edges while there is more to scroll sideways.
(() => {
const SELECTOR = '.kodza-table';
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const ease = 'cubic-bezier(0.22, 1, 0.36, 1)';
// One instance. Everything it starts is tied to `signal`, so kodza:destroy stops it all
function mount(root, signal) {
const tbody = root.querySelector('tbody');
const headers = [...root.querySelectorAll('thead th')];
if (!tbody) return;
const value = (row, index) => {
const cell = row.children[index];
const raw = cell?.dataset.sort ?? cell?.textContent.trim() ?? '';
const number = Number(raw);
return raw !== '' && !Number.isNaN(number) ? number : raw.toLowerCase();
};
const mark = (index) => [...tbody.rows].forEach((row) => [...row.children].forEach((cell, i) => cell.classList.toggle('is-sorted', i === index)));
const sort = (th) => {
const index = headers.indexOf(th);
const numeric = th.classList.contains('kodza-table__num');
const current = th.getAttribute('aria-sort');
// A new column starts from the most telling end: biggest numbers, first letters
const order = current ? (current === 'ascending' ? 'descending' : 'ascending') : numeric ? 'descending' : 'ascending';
headers.forEach((other) => other !== th && other.removeAttribute('aria-sort'));
th.setAttribute('aria-sort', order);
const rows = [...tbody.rows];
const k = root.getBoundingClientRect().width / root.offsetWidth || 1;
const before = new Map(rows.map((row) => [row, row.getBoundingClientRect().top]));
rows.sort((a, b) => {
const x = value(a, index);
const y = value(b, index);
const result = typeof x === 'number' && typeof y === 'number' ? x - y : String(x).localeCompare(String(y));
return order === 'ascending' ? result : -result;
});
tbody.append(...rows);
mark(index);
if (reduceMotion) return;
rows.forEach((row, i) => {
const dy = (before.get(row) - row.getBoundingClientRect().top) / k;
if (dy) row.animate([{ transform: `translateY(${dy}px)` }, { transform: 'none' }], { duration: 620, delay: Math.min(i, 8) * 18, easing: ease, fill: 'backwards' });
});
};
headers.forEach((th) => th.querySelector('.kodza-table__sort')?.addEventListener('click', () => sort(th), { signal }));
const sorted = headers.find((th) => th.hasAttribute('aria-sort'));
if (sorted) mark(headers.indexOf(sorted));
// The edges fade while there is more to see sideways
const edges = () => {
const more = root.scrollWidth - root.clientWidth;
root.style.setProperty('--kz-table-fade-l', root.scrollLeft > 2 ? '32px' : '0px');
root.style.setProperty('--kz-table-fade-r', more - root.scrollLeft > 2 ? '32px' : '0px');
};
edges();
root.addEventListener('scroll', edges, { passive: true, signal });
const observer = new ResizeObserver(edges);
observer.observe(root);
signal.addEventListener('abort', () => observer.disconnect());
}
// Kodza lifecycle, the same in every component: `kodza:init` starts the instances inside
// event.detail.root (the whole page by default), `kodza:destroy` stops them and puts their
// markup back. For themes and apps that swap content without a page load.
const instances = new WeakMap();
const find = (scope = document) => [scope, ...scope.querySelectorAll(SELECTOR)].filter((el) => el.matches?.(SELECTOR));
const init = (scope) => find(scope).forEach((root) => {
if (root.classList.contains('is-ready')) return; // already running, maybe from another copy of this script
const controller = new AbortController();
instances.set(root, { controller, html: root.innerHTML, attrs: [...root.attributes].map((a) => [a.name, a.value]) });
mount(root, controller.signal);
root.classList.add('is-ready');
});
const destroy = (scope) => find(scope).forEach((root) => {
const instance = instances.get(root);
if (!instance) return;
instances.delete(root);
instance.controller.abort();
[...root.attributes].forEach((a) => root.removeAttribute(a.name));
instance.attrs.forEach(([name, value]) => root.setAttribute(name, value));
root.innerHTML = instance.html;
});
document.addEventListener('kodza:init', (event) => init(event.detail?.root));
document.addEventListener('kodza:destroy', (event) => destroy(event.detail?.root));
init();
// Loaded in <head>, the script runs before the markup exists: once more when it is parsed
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init(), { once: true });
})();
'use client';
// Kodza — Table, for React. From https://kodza.dev/components/table/
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Next to this file: table.css and table.js, the component's CSS and JS from Kodza, as they are.
// Yours to edit: the markup is plain JSX below.
// The script changes this markup once React has rendered it (it adds layers, splits text, moves
// nodes), so React must not patch it afterwards. When a prop changes, the key gives React a fresh
// element: kodza:destroy stops the script on the old one, kodza:init starts it on the new one.
import { useRef, useEffect, type ComponentPropsWithoutRef, type ComponentRef, type CSSProperties } from 'react';
import './table.css';
export type TableProps = Omit<ComponentPropsWithoutRef<'div'>, "children" | "vars"> & {
/** Settings, as CSS variables on the root */
vars?: {
/** default: #ffffff */
"--kz-table-bg"?: string | number;
/** default: #0a0a0a */
"--kz-table-fg"?: string | number;
/** default: rgba(10, 10, 10, 0.6) */
"--kz-table-muted"?: string | number;
/** default: rgba(10, 10, 10, 0.09) */
"--kz-table-line"?: string | number;
/** default: rgba(10, 10, 10, 0.1) */
"--kz-table-border"?: string | number;
/** default: rgba(10, 10, 10, 0.035) */
"--kz-table-soft"?: string | number;
/** default: #0a0a0a */
"--kz-table-ink"?: string | number;
/** default: #ffffff */
"--kz-table-on-ink"?: string | number;
/** default: none */
"--kz-table-max"?: string | number;
/** default: cubic-bezier(0.22, 1, 0.36, 1) */
"--kz-table-ease"?: string | number;
};
className?: string;
};
export function Table({ vars, className, style, ...rest }: TableProps) {
const ref = useRef<ComponentRef<'div'>>(null);
const classes = ["kodza-table", className].filter(Boolean).join(' ');
// Everything the markup is made of: when it changes, React gets a fresh element (see the top)
const key = JSON.stringify([classes, vars, style, rest]);
// Starts the script on this element once React has rendered it, stops it when it goes
useEffect(() => {
const root = ref.current;
let live = true;
// @ts-ignore: a plain script, no types to find
import('./table.js').then(() => {
if (live) document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root } }));
});
return () => {
live = false;
document.dispatchEvent(new CustomEvent('kodza:destroy', { detail: { root } }));
};
}, [key]);
return (
<div role="region" aria-label="Most copied components" tabIndex={0} key={key} {...rest} className={classes} style={{ ...vars, ...style } as CSSProperties} ref={ref}>
<table>
<thead>
<tr>
<th scope="col">
<button className="kodza-table__sort" type="button">Component<svg viewBox="0 0 12 12" aria-hidden="true"> <path d="M6 2.5v7M3 6.5l3 3 3-3" /> </svg></button>
</th>
<th scope="col">
<button className="kodza-table__sort" type="button">Category<svg viewBox="0 0 12 12" aria-hidden="true"> <path d="M6 2.5v7M3 6.5l3 3 3-3" /> </svg></button>
</th>
<th scope="col" className="kodza-table__num">
<button className="kodza-table__sort" type="button">Size<svg viewBox="0 0 12 12" aria-hidden="true"> <path d="M6 2.5v7M3 6.5l3 3 3-3" /> </svg></button>
</th>
<th scope="col" className="kodza-table__num" aria-sort="descending">
<button className="kodza-table__sort" type="button">Copies<svg viewBox="0 0 12 12" aria-hidden="true"> <path d="M6 2.5v7M3 6.5l3 3 3-3" /> </svg></button>
</th>
<th scope="col" className="kodza-table__num">
<button className="kodza-table__sort" type="button">Updated<svg viewBox="0 0 12 12" aria-hidden="true"> <path d="M6 2.5v7M3 6.5l3 3 3-3" /> </svg></button>
</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span className="kodza-table__name">Fill Button</span>
<span className="kodza-table__slug">button-fill</span>
</td>
<td>Buttons</td>
<td className="kodza-table__num" data-sort="2.1">2.1 KB</td>
<td className="kodza-table__num" data-sort="12480">12,480</td>
<td className="kodza-table__num" data-sort="2026-10-02">
<time dateTime="2026-10-02">Oct 2</time>
</td>
<td>
<span className="kodza-table__status" data-status="stable">Stable</span>
</td>
</tr>
<tr>
<td>
<span className="kodza-table__name">Toast</span>
<span className="kodza-table__slug">toast-notification</span>
</td>
<td>Overlays</td>
<td className="kodza-table__num" data-sort="6.8">6.8 KB</td>
<td className="kodza-table__num" data-sort="9312">9,312</td>
<td className="kodza-table__num" data-sort="2026-10-16">
<time dateTime="2026-10-16">Oct 16</time>
</td>
<td>
<span className="kodza-table__status" data-status="stable">Stable</span>
</td>
</tr>
<tr>
<td>
<span className="kodza-table__name">Command Palette</span>
<span className="kodza-table__slug">command-palette</span>
</td>
<td>Navigation</td>
<td className="kodza-table__num" data-sort="9.4">9.4 KB</td>
<td className="kodza-table__num" data-sort="7045">7,045</td>
<td className="kodza-table__num" data-sort="2026-10-14">
<time dateTime="2026-10-14">Oct 14</time>
</td>
<td>
<span className="kodza-table__status" data-status="beta">Beta</span>
</td>
</tr>
<tr>
<td>
<span className="kodza-table__name">Skeleton</span>
<span className="kodza-table__slug">skeleton-loader</span>
</td>
<td>Feedback</td>
<td className="kodza-table__num" data-sort="4.2">4.2 KB</td>
<td className="kodza-table__num" data-sort="5980">5,980</td>
<td className="kodza-table__num" data-sort="2026-10-16">
<time dateTime="2026-10-16">Oct 16</time>
</td>
<td>
<span className="kodza-table__status" data-status="new">New</span>
</td>
</tr>
<tr>
<td>
<span className="kodza-table__name">Image Gallery</span>
<span className="kodza-table__slug">image-gallery</span>
</td>
<td>Images</td>
<td className="kodza-table__num" data-sort="7.7">7.7 KB</td>
<td className="kodza-table__num" data-sort="5214">5,214</td>
<td className="kodza-table__num" data-sort="2026-09-28">
<time dateTime="2026-09-28">Sep 28</time>
</td>
<td>
<span className="kodza-table__status" data-status="stable">Stable</span>
</td>
</tr>
<tr>
<td>
<span className="kodza-table__name">Date Picker</span>
<span className="kodza-table__slug">date-picker</span>
</td>
<td>Forms</td>
<td className="kodza-table__num" data-sort="11.2">11.2 KB</td>
<td className="kodza-table__num" data-sort="4871">4,871</td>
<td className="kodza-table__num" data-sort="2026-10-12">
<time dateTime="2026-10-12">Oct 12</time>
</td>
<td>
<span className="kodza-table__status" data-status="beta">Beta</span>
</td>
</tr>
<tr>
<td>
<span className="kodza-table__name">Marquee</span>
<span className="kodza-table__slug">scrolling-text-loop</span>
</td>
<td>Text</td>
<td className="kodza-table__num" data-sort="1.3">1.3 KB</td>
<td className="kodza-table__num" data-sort="3902">3,902</td>
<td className="kodza-table__num" data-sort="2026-08-30">
<time dateTime="2026-08-30">Aug 30</time>
</td>
<td>
<span className="kodza-table__status" data-status="stable">Stable</span>
</td>
</tr>
<tr>
<td>
<span className="kodza-table__name">Alert Dialog</span>
<span className="kodza-table__slug">alert-dialog</span>
</td>
<td>Overlays</td>
<td className="kodza-table__num" data-sort="3.9">3.9 KB</td>
<td className="kodza-table__num" data-sort="1204">1,204</td>
<td className="kodza-table__num" data-sort="2026-03-03">
<time dateTime="2026-03-03">Mar 3</time>
</td>
<td>
<span className="kodza-table__status" data-status="deprecated">Deprecated</span>
</td>
</tr>
</tbody>
</table>
</div>
);
}
Checked
Robots poked at it before you did. Every component goes through the same checks, and again each time it changes.