Table
wu-table styles a data <table>. Wide tables go in a wu-table-scroll region, which shows shadows on the sides that have more content. The select-all box and expandable rows need the kit script; everything else is CSS.
| Class, attribute or variable | Effect |
|---|---|
wu-table |
Root. Full width, row lines, muted header |
wu-table-num |
Part, on cells. End-aligned tabular figures |
wu-table-sort |
Part, a <button> in a header cell. Sort arrows drawn in CSS |
aria-sort on a header cell |
ascending or descending shows one arrow |
wu-table-select |
Part, on the cells of a checkbox column. A checked box selects its row; the script keeps the header box in step |
| Selected row | A checked wu-table-select box, or aria-selected="true" on a row of a role="grid" table. Tint and a bar at the start of the first cell; wins over striped and hover |
wu-table-expand |
Part, a <button> with aria-expanded. The script shows or hides the detail row |
wu-table-detail |
Part, a <tr hidden> right after its row, with one cell spanning all columns |
wu-table-truncate |
Part, on a cell whose text sits in a child element. One line, ellipsis at the inline end |
--wu-table--TruncateWidth |
Widest a truncated text gets, 16rem by default |
<tfoot> |
Summary row: semibold, with a strong line above it |
sm |
Less cell padding |
striped |
Tint on even rows |
hover |
Tint on the row under the pointer |
bordered |
Outer border, rounded corners, lines between cells |
sticky-header |
Header cells stick to the top, with a shadow once stuck |
sticky-start |
First column sticks to the inline start of the scroll region |
sticky-end |
Last column sticks to the inline end of the scroll region |
animated |
The expand chevron turns, opt-in |
wu-table-scroll |
Scroll region with scroll shadows |
--wu-table--MaxHeight |
Height of the scroll region, so the header sticks inside it |
--wu-table--Background |
Background of the scroll region and the sticky header |
Default
Section titled “Default”| Invoice | Customer | Amount |
|---|---|---|
| INV-1042 | Northwind | $1,200.00 |
| INV-1043 | Contoso | $86.50 |
| INV-1044 | Fabrikam | $12,940.00 |
<table class="wu-table"> <caption>Invoices this quarter</caption> <thead> <tr><th scope="col">Invoice</th><th scope="col">Customer</th><th scope="col" class="wu-table-num">Amount</th></tr> </thead> <tbody> <tr><th scope="row">INV-1042</th><td>Northwind</td><td class="wu-table-num">$1,200.00</td></tr> <tr><th scope="row">INV-1043</th><td>Contoso</td><td class="wu-table-num">$86.50</td></tr> <tr><th scope="row">INV-1044</th><td>Fabrikam</td><td class="wu-table-num">$12,940.00</td></tr> </tbody></table>Sorting
Section titled “Sorting”A wu-table-sort button in the header cell shows both arrows dimmed; aria-sort on the cell shows one. Sorting the rows and updating aria-sort is up to your script.
| Ana Lopez | Owner | 12 |
| Ben Okafor | Editor | 7 |
| Chen Wei | Viewer | 3 |
<table class="wu-table"> <thead> <tr> <th scope="col" aria-sort="ascending"><button class="wu-table-sort" type="button">Name</button></th> <th scope="col"><button class="wu-table-sort" type="button">Role</button></th> <th scope="col" class="wu-table-num"><button class="wu-table-sort" type="button">Projects</button></th> </tr> </thead> <tbody> <tr><td>Ana Lopez</td><td>Owner</td><td class="wu-table-num">12</td></tr> <tr><td>Ben Okafor</td><td>Editor</td><td class="wu-table-num">7</td></tr> <tr><td>Chen Wei</td><td>Viewer</td><td class="wu-table-num">3</td></tr> </tbody></table>Compact, striped and hover
Section titled “Compact, striped and hover”| Region | Orders | Revenue |
|---|---|---|
| North | 320 | $41,200 |
| South | 214 | $27,850 |
| East | 198 | $24,100 |
| West | 275 | $35,640 |
<table class="wu-table sm striped hover"> <thead> <tr><th scope="col">Region</th><th scope="col" class="wu-table-num">Orders</th><th scope="col" class="wu-table-num">Revenue</th></tr> </thead> <tbody> <tr><td>North</td><td class="wu-table-num">320</td><td class="wu-table-num">$41,200</td></tr> <tr><td>South</td><td class="wu-table-num">214</td><td class="wu-table-num">$27,850</td></tr> <tr><td>East</td><td class="wu-table-num">198</td><td class="wu-table-num">$24,100</td></tr> <tr><td>West</td><td class="wu-table-num">275</td><td class="wu-table-num">$35,640</td></tr> </tbody></table>Selection
Section titled “Selection”A wu-table-select cell holds a checkbox. A checked box selects its row: the row keeps its tint on a striped row and under the pointer, and a bar marks it at the start. The header box selects or clears every row, and the script shows it as mixed when only some rows are selected.
| Name | Role | Projects | |
|---|---|---|---|
| Ana Lopez | Owner | 12 | |
| Ben Okafor | Editor | 7 | |
| Chen Wei | Viewer | 3 |
<table class="wu-table striped hover"> <thead> <tr> <th scope="col" class="wu-table-select"><input type="checkbox" class="wu-checkbox-input" aria-label="Select all rows"></th> <th scope="col">Name</th><th scope="col">Role</th><th scope="col" class="wu-table-num">Projects</th> </tr> </thead> <tbody> <tr><td class="wu-table-select"><input type="checkbox" class="wu-checkbox-input" aria-label="Select Ana Lopez"></td><td>Ana Lopez</td><td>Owner</td><td class="wu-table-num">12</td></tr> <tr><td class="wu-table-select"><input type="checkbox" class="wu-checkbox-input" aria-label="Select Ben Okafor" checked></td><td>Ben Okafor</td><td>Editor</td><td class="wu-table-num">7</td></tr> <tr><td class="wu-table-select"><input type="checkbox" class="wu-checkbox-input" aria-label="Select Chen Wei"></td><td>Chen Wei</td><td>Viewer</td><td class="wu-table-num">3</td></tr> </tbody></table>Listen for change on the table and read the checked boxes. Code that sets checked itself should then dispatch a bubbling change event on the box, so the header box follows.
Expandable rows
Section titled “Expandable rows”A wu-table-expand button toggles the wu-table-detail row right after its row. The closed chevron points to the inline end, the open one points down. With animated, the chevron turns.
| Order | Customer | Total |
|---|---|---|
| 3021 | Northwind | $320.00 |
| Two chairs and a desk, shipped from the Leeds warehouse. | ||
| 3022 | Contoso | $86.50 |
| One desk lamp, delivered on 4 March. | ||
<table class="wu-table animated"> <thead> <tr><th scope="col">Order</th><th scope="col">Customer</th><th scope="col" class="wu-table-num">Total</th></tr> </thead> <tbody> <tr> <th scope="row"><button class="wu-table-expand" type="button" aria-expanded="false" aria-label="Items in order 3021"></button>3021</th> <td>Northwind</td><td class="wu-table-num">$320.00</td> </tr> <tr class="wu-table-detail" hidden><td colspan="3">Two chairs and a desk, shipped from the Leeds warehouse.</td></tr> <tr> <th scope="row"><button class="wu-table-expand" type="button" aria-expanded="true" aria-label="Items in order 3022"></button>3022</th> <td>Contoso</td><td class="wu-table-num">$86.50</td> </tr> <tr class="wu-table-detail"><td colspan="3">One desk lamp, delivered on 4 March.</td></tr> </tbody></table>The script finds the detail row through aria-controls, or takes the next row, and sets aria-controls, aria-expanded and hidden.
Truncated cells
Section titled “Truncated cells”wu-table-truncate keeps a long text on one line with an ellipsis. Put the text in a child element and the full text in core’s data-tooltip on the cell; tabindex="0" lets keyboard users open the tooltip too.
| Ticket | Summary | Status |
|---|---|---|
| T-118 | Typo on the billing page | Open |
| T-119 | Export to CSV drops the last row when the report has more than one page | Closed |
<table class="wu-table"> <thead> <tr><th scope="col">Ticket</th><th scope="col">Summary</th><th scope="col">Status</th></tr> </thead> <tbody> <tr> <td>T-118</td> <td class="wu-table-truncate" tabindex="0" data-tooltip="Typo on the billing page" data-tooltip-size="large" style="--wu-table--TruncateWidth: 7rem"><span>Typo on the billing page</span></td> <td>Open</td> </tr> <tr> <td>T-119</td> <td class="wu-table-truncate" tabindex="0" data-tooltip="Export to CSV drops the last row when the report has more than one page" data-tooltip-size="large" style="--wu-table--TruncateWidth: 7rem"><span>Export to CSV drops the last row when the report has more than one page</span></td> <td>Closed</td> </tr> </tbody></table>Summary row
Section titled “Summary row”Rows in <tfoot> are semibold, with a strong line above them.
| Item | Qty | Amount |
|---|---|---|
| Hosting | 1 | $49.00 |
| Extra seats | 3 | $36.00 |
| Total | 4 | $85.00 |
<table class="wu-table"> <thead> <tr><th scope="col">Item</th><th scope="col" class="wu-table-num">Qty</th><th scope="col" class="wu-table-num">Amount</th></tr> </thead> <tbody> <tr><td>Hosting</td><td class="wu-table-num">1</td><td class="wu-table-num">$49.00</td></tr> <tr><td>Extra seats</td><td class="wu-table-num">3</td><td class="wu-table-num">$36.00</td></tr> </tbody> <tfoot> <tr><th scope="row">Total</th><td class="wu-table-num">4</td><td class="wu-table-num">$85.00</td></tr> </tfoot></table>Bordered
Section titled “Bordered”| Plan | Seats | Price |
|---|---|---|
| Starter | 1 | $0 |
| Team | 10 | $49 |
<table class="wu-table bordered"> <thead> <tr><th scope="col">Plan</th><th scope="col">Seats</th><th scope="col" class="wu-table-num">Price</th></tr> </thead> <tbody> <tr><td>Starter</td><td>1</td><td class="wu-table-num">$0</td></tr> <tr><td>Team</td><td>10</td><td class="wu-table-num">$49</td></tr> </tbody></table>Scroll region and sticky header
Section titled “Scroll region and sticky header”A scroll region must be reachable by keyboard: give it tabindex="0", role="region" and an aria-label. With --wu-table--MaxHeight set, the region scrolls vertically too and a sticky-header table keeps its header in view.
| Month | API calls | Storage | Bandwidth | Build minutes | Seats |
|---|---|---|---|---|---|
| January | 1,204,331 | 48 GB | 1.2 TB | 3,120 | 8 |
| February | 1,309,870 | 51 GB | 1.4 TB | 3,410 | 8 |
| March | 1,422,019 | 55 GB | 1.5 TB | 3,980 | 9 |
| April | 1,501,442 | 58 GB | 1.6 TB | 4,050 | 10 |
| May | 1,630,005 | 62 GB | 1.8 TB | 4,420 | 10 |
| June | 1,712,860 | 66 GB | 1.9 TB | 4,700 | 11 |
<div class="wu-table-scroll" tabindex="0" role="region" aria-label="Monthly usage" style="--wu-table--MaxHeight: 12rem"> <table class="wu-table sticky-header"> <thead> <tr><th scope="col">Month</th><th scope="col" class="wu-table-num">API calls</th><th scope="col" class="wu-table-num">Storage</th><th scope="col" class="wu-table-num">Bandwidth</th><th scope="col" class="wu-table-num">Build minutes</th><th scope="col" class="wu-table-num">Seats</th></tr> </thead> <tbody> <tr><td>January</td><td class="wu-table-num">1,204,331</td><td class="wu-table-num">48 GB</td><td class="wu-table-num">1.2 TB</td><td class="wu-table-num">3,120</td><td class="wu-table-num">8</td></tr> <tr><td>February</td><td class="wu-table-num">1,309,870</td><td class="wu-table-num">51 GB</td><td class="wu-table-num">1.4 TB</td><td class="wu-table-num">3,410</td><td class="wu-table-num">8</td></tr> <tr><td>March</td><td class="wu-table-num">1,422,019</td><td class="wu-table-num">55 GB</td><td class="wu-table-num">1.5 TB</td><td class="wu-table-num">3,980</td><td class="wu-table-num">9</td></tr> <tr><td>April</td><td class="wu-table-num">1,501,442</td><td class="wu-table-num">58 GB</td><td class="wu-table-num">1.6 TB</td><td class="wu-table-num">4,050</td><td class="wu-table-num">10</td></tr> <tr><td>May</td><td class="wu-table-num">1,630,005</td><td class="wu-table-num">62 GB</td><td class="wu-table-num">1.8 TB</td><td class="wu-table-num">4,420</td><td class="wu-table-num">10</td></tr> <tr><td>June</td><td class="wu-table-num">1,712,860</td><td class="wu-table-num">66 GB</td><td class="wu-table-num">1.9 TB</td><td class="wu-table-num">4,700</td><td class="wu-table-num">11</td></tr> </tbody> </table></div>Sticky columns and right to left
Section titled “Sticky columns and right to left”In a scroll region, sticky-start keeps the first column in view and sticky-end the last, such as a name column and an actions column. A shadow shows on the column’s inner edge while content is hidden under it, on the correct side in both directions. With a selection column, sticky-start keeps the checkboxes in view. A selected row’s bar and a truncated cell’s ellipsis follow the text direction too.
| שם | אימייל | צוות | מיקום | פרויקטים | פעולות |
|---|---|---|---|---|---|
| דנה לוי | [email protected] | הנדסת פלטפורמה | ליסבון, פורטוגל | 12 | |
| בן כהן | [email protected] | הצלחת לקוחות | לגוס, ניגריה | 7 | |
| חן וייס | [email protected] | נתונים וניתוח | סינגפור | 3 |
<div class="wu-table-scroll" tabindex="0" role="region" aria-label="חברי הצוות"> <table class="wu-table sticky-start sticky-end"> <thead> <tr><th scope="col">שם</th><th scope="col">אימייל</th><th scope="col">צוות</th><th scope="col">מיקום</th><th scope="col" class="wu-table-num">פרויקטים</th><th scope="col">פעולות</th></tr> </thead> <tbody> <tr><th scope="row">דנה לוי</th><td>[email protected]</td><td>הנדסת פלטפורמה</td><td>ליסבון, פורטוגל</td><td class="wu-table-num">12</td><td><button class="wu-btn sm" type="button">עריכה</button></td></tr> <tr><th scope="row">בן כהן</th><td>[email protected]</td><td>הצלחת לקוחות</td><td>לגוס, ניגריה</td><td class="wu-table-num">7</td><td><button class="wu-btn sm" type="button">עריכה</button></td></tr> <tr><th scope="row">חן וייס</th><td>[email protected]</td><td>נתונים וניתוח</td><td>סינגפור</td><td class="wu-table-num">3</td><td><button class="wu-btn sm" type="button">עריכה</button></td></tr> </tbody> </table></div>Accessibility
Section titled “Accessibility”aria-selectedon a<tr>is valid only in arole="grid"orrole="treegrid"table, which also needs grid keyboard navigation. In a plain table the checkbox carries the selection, so screen readers announce it: label each box with its row (Select Ana Lopez) and the header boxSelect all rows. The script sets the header box to checked, mixed or clear, and in arole="grid"table it also setsaria-selectedon each row.- Give each expand button an
aria-labelthat names its row. The script setsaria-expandedandaria-controls. - Screen readers read the full text of a truncated cell. A scroll region clips tooltips at its edges.
- A scroll region must be reachable by keyboard:
tabindex="0",role="region"and anaria-label.
Load only the table
Section titled “Load only the table”@use "@wovenui/components/table";The table needs no other component.