Skip to content

Toggle Checkbox Data Table

A header checkbox with controls selects every row, and goes indeterminate when only some rows are picked. A checkedchange listener on every checkbox updates the row highlight and the bulk action bar, whether a row or the header changed it.

No rows selected
Invoice Amount
INV-1042 $1,200.00
INV-1043 $480.00
INV-1044 $3,150.00

Code

The following example assumes that you have imported the component and set up the theme.

Data Table
<div class="data-table">
36 collapsed lines
<div class="toolbar">
<span class="count">No rows selected</span>
<button type="button" class="delete" disabled>Delete</button>
</div>
<table>
<thead>
<tr>
<th class="pick">
<sv-toggle-checkbox aria-label="Select all rows" controls="row-1 row-2 row-3"></sv-toggle-checkbox>
</th>
<th>Invoice</th>
<th class="amount">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td class="pick"><sv-toggle-checkbox id="row-1" aria-label="Select INV-1042"></sv-toggle-checkbox></td>
<td>INV-1042</td>
<td class="amount">$1,200.00</td>
</tr>
<tr>
<td class="pick"><sv-toggle-checkbox id="row-2" aria-label="Select INV-1043"></sv-toggle-checkbox></td>
<td>INV-1043</td>
<td class="amount">$480.00</td>
</tr>
<tr>
<td class="pick"><sv-toggle-checkbox id="row-3" aria-label="Select INV-1044"></sv-toggle-checkbox></td>
<td>INV-1044</td>
<td class="amount">$3,150.00</td>
</tr>
</tbody>
</table>
</div>
<script type="application/x-typescript" module>
19 collapsed lines
const table = document.querySelector(".data-table")!;
const rowCheckboxes = table.querySelectorAll<ToggleCheckbox>("tbody sv-toggle-checkbox");
const count = table.querySelector(".count")!;
const deleteButton = table.querySelector<HTMLButtonElement>(".delete")!;
const update = () => {
const selected = [...rowCheckboxes].filter(checkbox => checkbox.checked);
for (const checkbox of rowCheckboxes) {
checkbox.closest("tr")?.classList.toggle("selected", checkbox.checked);
}
count.textContent = selected.length === 0 ? "No rows selected" : `${selected.length} selected`;
deleteButton.disabled = selected.length === 0;
};
for (const checkbox of table.querySelectorAll<ToggleCheckbox>("sv-toggle-checkbox")) {
checkbox.addEventListener("checkedchange", update);
}
</script>
<style>
79 collapsed lines
.data-table {
max-inline-size: 28rem;
margin-inline: auto;
overflow: hidden;
background-color: var(--sv-gray-7);
border: 1px solid var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-lg);
.toolbar {
display: flex;
gap: 1em;
align-items: center;
justify-content: space-between;
padding: 0.75em 1em;
border-block-end: 1px solid var(--sv-gray-5);
.count {
font-size: 0.9em;
color: var(--sv-gray-3);
}
.delete {
padding: 0.4em 0.9em;
font: inherit;
color: var(--sv-accent-content);
cursor: pointer;
background-color: var(--sv-accent);
border: none;
border-radius: var(--sv-bdr-rad-md);
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
}
table {
inline-size: 100%;
border-collapse: collapse;
}
th,
td {
padding: 0.7em 1em;
vertical-align: middle;
text-align: start;
}
th {
font-size: 0.85em;
color: var(--sv-gray-3);
text-transform: uppercase;
}
.pick {
inline-size: 1em;
text-align: center;
sv-toggle-checkbox {
display: block;
margin-inline: auto;
}
}
.amount {
font-variant-numeric: tabular-nums;
text-align: end;
}
tbody tr {
border-block-start: 1px solid var(--sv-gray-5);
transition: background-color 200ms;
&.selected {
background-color: color-mix(in srgb, var(--sv-accent) 12%, transparent);
}
}
}
</style>