COOKBOOK / LIVE EXAMPLE
Table Selection example with VSN.js
Real markup. Real VSN. Try it below.
← All examples · Open full screen ↗
View example source
<section id="accessible-table-example" class="example-card cookbook-example">
<header class="cookbook-heading">
<div>
<p class="cookbook-eyebrow">Behavior cookbook</p>
<h1>Accessible table selection</h1>
<p class="cookbook-lede">
Row scopes drive checkbox state, selected styling, and a mixed selection summary.
</p>
</div>
<span class="cookbook-badge">rows + ARIA</span>
</header>
<div class="cookbook-toolbar">
<button class="btn secondary" type="button" aria-pressed="false" vsn-on:click="toggleAll();">
Select all
</button>
<span class="cookbook-status">
<strong vsn-bind:from="selectedCount">0</strong> selected
</span>
<span class="cookbook-note-inline">The button exposes true, false, or mixed selection through <code>aria-pressed</code>.</span>
</div>
<div class="cookbook-table-wrap">
<table class="cookbook-table">
<caption>Example users</caption>
<thead>
<tr>
<th scope="col">Select</th>
<th scope="col">Name</th>
<th scope="col">Role</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<template vsn-each="rows as row, index" vsn-key="row.0">
<tr class="cookbook-table-row">
<td>
<input class="cookbook-table-check" type="checkbox" data-row-id="" />
</td>
<th scope="row" vsn-bind:from="row.1">Ada Lovelace</th>
<td vsn-bind:from="row.2">Editor</td>
<td><span class="cookbook-table-status" vsn-bind:from="row.3">Active</span></td>
</tr>
</template>
</tbody>
</table>
</div>
<p class="cookbook-note">
The row behavior reads <code>row</code> and <code>index</code> from the <code>vsn-each</code> child scope while selection lives at the root.
</p>
</section>
<script type="text/vsn">
#accessible-table-example {
rows: [
["u1", "Ada Lovelace", "Editor", "Active"],
["u2", "Grace Hopper", "Maintainer", "Active"],
["u3", "Linus Torvalds", "Reviewer", "Invited"]
];
selected: [];
selectedCount: 0;
allSelected: false;
sync() {
selectedCount = selected.length;
allSelected = selectedCount == rows.length;
}
isSelected(id) {
return selected.includes(id);
}
toggle(id) {
if (isSelected(id)) {
selected = selected.filter((value) => value != id);
} else {
selected = selected.concat([id]);
}
sync();
}
toggleAll() {
selected = allSelected ? [] : ["u1", "u2", "u3"];
sync();
}
.cookbook-table-row {
@class :< { "is-selected": selected && selected.includes(row[0]) };
}
.cookbook-table-check {
@data-row-id :< row[0];
@checked :< (selected && selected.includes(row[0]));
@aria-label :< "Select " + row[1];
on change() {
toggle(row[0]);
}
}
.cookbook-toolbar .btn {
@aria-pressed :< allSelected;
}
}
</script>