Table
Styles an authored <table> in place: write the caption, thead, tbody, and th scope yourself, and the table reads as a table before JavaScript and to a screen reader. A cell's data-ui-align (start, center, or end; start when unset) sets its content along the row, such as end for a column of figures; compose a value with a muted line beneath it from Text. Table is presentation only: it does not sort, page, or select rows.
Recent updates
A compact two-column table with readable headings and two rows.
| Page | Status |
|---|---|
| Roadmap | Draft |
| Guide | Ready |
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-table/ui-table.html"
>
<ui-table>
<table>
<caption>Recent updates</caption>
<thead>
<tr>
<th
scope="col"
>Page</th>
<th
scope="col"
>Status</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Roadmap</th>
<td>Draft</td>
</tr>
<tr>
<th
scope="row"
>Guide</th>
<td>Ready</td>
</tr>
</tbody>
</table>
</ui-table><script setup>
import { UiTable } from "@threadlabs/looma";
</script>
<template>
<UiTable>
<table>
<caption>Recent updates</caption>
<thead>
<tr>
<th
scope="col"
>Page</th>
<th
scope="col"
>Status</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Roadmap</th>
<td>Draft</td>
</tr>
<tr>
<th
scope="row"
>Guide</th>
<td>Ready</td>
</tr>
</tbody>
</table>
</UiTable>
</template>import { UiTable } from "@threadlabs/looma";
export default function Example() {
return <UiTable>
<table>
<caption>Recent updates</caption>
<thead>
<tr>
<th
scope="col"
>Page</th>
<th
scope="col"
>Status</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Roadmap</th>
<td>Draft</td>
</tr>
<tr>
<th
scope="row"
>Guide</th>
<td>Ready</td>
</tr>
</tbody>
</table>
</UiTable>;
}/** @jsxImportSource preact */
import { UiTable } from "@threadlabs/looma";
export default function Example() {
return <UiTable>
<table>
<caption>Recent updates</caption>
<thead>
<tr>
<th
scope="col"
>Page</th>
<th
scope="col"
>Status</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Roadmap</th>
<td>Draft</td>
</tr>
<tr>
<th
scope="row"
>Guide</th>
<td>Ready</td>
</tr>
</tbody>
</table>
</UiTable>;
}<script>
import { UiTable } from "@threadlabs/looma";
</script>
<UiTable>
<table>
<caption>Recent updates</caption>
<thead>
<tr>
<th
scope="col"
>Page</th>
<th
scope="col"
>Status</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Roadmap</th>
<td>Draft</td>
</tr>
<tr>
<th
scope="row"
>Guide</th>
<td>Ready</td>
</tr>
</tbody>
</table>
</UiTable>import { Component } from "@angular/core";
import { UiTable } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiTable],
template: `
<div
ui-table
>
<table>
<caption>Recent updates</caption>
<thead>
<tr>
<th
scope="col"
>Page</th>
<th
scope="col"
>Status</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Roadmap</th>
<td>Draft</td>
</tr>
<tr>
<th
scope="row"
>Guide</th>
<td>Ready</td>
</tr>
</tbody>
</table>
</div>
`,
})
export default class Example {}Default
An authored table, styled in place: a caption names it, th scope marks column and row headers, and data-ui-align="end" sets a column of figures at the end edge. A cell can hold a value with a muted line beneath it, composed from Text.
| Order | Customer | Status | Total |
|---|---|---|---|
ORD-20931 Placed 2 April | Northwind Traders | Paid | $1,240.00 |
ORD-20932 Placed 3 April | Contoso | Due | $86.50 |
ORD-20933 Placed 3 April | Fabrikam | Draft | $12,000.00 |
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-table/ui-table.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-badge/ui-badge.html"
>
<ui-table>
<table>
<caption>Recent orders</caption>
<thead>
<tr>
<th
scope="col"
>Order</th>
<th
scope="col"
>Customer</th>
<th
scope="col"
>Status</th>
<th
scope="col"
data-ui-align="end"
>Total</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<ui-text
variant="code"
>ORD-20931</ui-text>
<ui-text
tone="muted"
size="xs"
>Placed 2 April</ui-text>
</th>
<td>Northwind Traders</td>
<td>
<ui-badge
tone="success"
>Paid</ui-badge>
</td>
<td
data-ui-align="end"
>$1,240.00</td>
</tr>
<tr>
<th
scope="row"
>
<ui-text
variant="code"
>ORD-20932</ui-text>
<ui-text
tone="muted"
size="xs"
>Placed 3 April</ui-text>
</th>
<td>Contoso</td>
<td>
<ui-badge
tone="warning"
>Due</ui-badge>
</td>
<td
data-ui-align="end"
>$86.50</td>
</tr>
<tr>
<th
scope="row"
>
<ui-text
variant="code"
>ORD-20933</ui-text>
<ui-text
tone="muted"
size="xs"
>Placed 3 April</ui-text>
</th>
<td>Fabrikam</td>
<td>
<ui-badge>Draft</ui-badge>
</td>
<td
data-ui-align="end"
>$12,000.00</td>
</tr>
</tbody>
</table>
</ui-table><script setup>
import { UiBadge, UiTable, UiText } from "@threadlabs/looma";
</script>
<template>
<UiTable>
<table>
<caption>Recent orders</caption>
<thead>
<tr>
<th
scope="col"
>Order</th>
<th
scope="col"
>Customer</th>
<th
scope="col"
>Status</th>
<th
scope="col"
data-ui-align="end"
>Total</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20931</UiText>
<UiText
tone="muted"
size="xs"
>Placed 2 April</UiText>
</th>
<td>Northwind Traders</td>
<td>
<UiBadge
tone="success"
>Paid</UiBadge>
</td>
<td
data-ui-align="end"
>$1,240.00</td>
</tr>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20932</UiText>
<UiText
tone="muted"
size="xs"
>Placed 3 April</UiText>
</th>
<td>Contoso</td>
<td>
<UiBadge
tone="warning"
>Due</UiBadge>
</td>
<td
data-ui-align="end"
>$86.50</td>
</tr>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20933</UiText>
<UiText
tone="muted"
size="xs"
>Placed 3 April</UiText>
</th>
<td>Fabrikam</td>
<td>
<UiBadge>Draft</UiBadge>
</td>
<td
data-ui-align="end"
>$12,000.00</td>
</tr>
</tbody>
</table>
</UiTable>
</template>import { UiBadge, UiTable, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiTable>
<table>
<caption>Recent orders</caption>
<thead>
<tr>
<th
scope="col"
>Order</th>
<th
scope="col"
>Customer</th>
<th
scope="col"
>Status</th>
<th
scope="col"
data-ui-align="end"
>Total</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20931</UiText>
<UiText
tone="muted"
size="xs"
>Placed 2 April</UiText>
</th>
<td>Northwind Traders</td>
<td>
<UiBadge
tone="success"
>Paid</UiBadge>
</td>
<td
data-ui-align="end"
>$1,240.00</td>
</tr>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20932</UiText>
<UiText
tone="muted"
size="xs"
>Placed 3 April</UiText>
</th>
<td>Contoso</td>
<td>
<UiBadge
tone="warning"
>Due</UiBadge>
</td>
<td
data-ui-align="end"
>$86.50</td>
</tr>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20933</UiText>
<UiText
tone="muted"
size="xs"
>Placed 3 April</UiText>
</th>
<td>Fabrikam</td>
<td>
<UiBadge>Draft</UiBadge>
</td>
<td
data-ui-align="end"
>$12,000.00</td>
</tr>
</tbody>
</table>
</UiTable>;
}/** @jsxImportSource preact */
import { UiBadge, UiTable, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiTable>
<table>
<caption>Recent orders</caption>
<thead>
<tr>
<th
scope="col"
>Order</th>
<th
scope="col"
>Customer</th>
<th
scope="col"
>Status</th>
<th
scope="col"
data-ui-align="end"
>Total</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20931</UiText>
<UiText
tone="muted"
size="xs"
>Placed 2 April</UiText>
</th>
<td>Northwind Traders</td>
<td>
<UiBadge
tone="success"
>Paid</UiBadge>
</td>
<td
data-ui-align="end"
>$1,240.00</td>
</tr>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20932</UiText>
<UiText
tone="muted"
size="xs"
>Placed 3 April</UiText>
</th>
<td>Contoso</td>
<td>
<UiBadge
tone="warning"
>Due</UiBadge>
</td>
<td
data-ui-align="end"
>$86.50</td>
</tr>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20933</UiText>
<UiText
tone="muted"
size="xs"
>Placed 3 April</UiText>
</th>
<td>Fabrikam</td>
<td>
<UiBadge>Draft</UiBadge>
</td>
<td
data-ui-align="end"
>$12,000.00</td>
</tr>
</tbody>
</table>
</UiTable>;
}<script>
import { UiBadge, UiTable, UiText } from "@threadlabs/looma";
</script>
<UiTable>
<table>
<caption>Recent orders</caption>
<thead>
<tr>
<th
scope="col"
>Order</th>
<th
scope="col"
>Customer</th>
<th
scope="col"
>Status</th>
<th
scope="col"
data-ui-align="end"
>Total</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20931</UiText>
<UiText
tone="muted"
size="xs"
>Placed 2 April</UiText>
</th>
<td>Northwind Traders</td>
<td>
<UiBadge
tone="success"
>Paid</UiBadge>
</td>
<td
data-ui-align="end"
>$1,240.00</td>
</tr>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20932</UiText>
<UiText
tone="muted"
size="xs"
>Placed 3 April</UiText>
</th>
<td>Contoso</td>
<td>
<UiBadge
tone="warning"
>Due</UiBadge>
</td>
<td
data-ui-align="end"
>$86.50</td>
</tr>
<tr>
<th
scope="row"
>
<UiText
variant="code"
>ORD-20933</UiText>
<UiText
tone="muted"
size="xs"
>Placed 3 April</UiText>
</th>
<td>Fabrikam</td>
<td>
<UiBadge>Draft</UiBadge>
</td>
<td
data-ui-align="end"
>$12,000.00</td>
</tr>
</tbody>
</table>
</UiTable>import { Component } from "@angular/core";
import { UiBadge, UiTable, UiText } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiTable, UiText, UiBadge],
template: `
<div
ui-table
>
<table>
<caption>Recent orders</caption>
<thead>
<tr>
<th
scope="col"
>Order</th>
<th
scope="col"
>Customer</th>
<th
scope="col"
>Status</th>
<th
scope="col"
data-ui-align="end"
>Total</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<ng-template
ui-text
[variant]="'code'"
>ORD-20931</ng-template>
<ng-template
ui-text
[tone]="'muted'"
[size]="'xs'"
>Placed 2 April</ng-template>
</th>
<td>Northwind Traders</td>
<td>
<span
ui-badge
[tone]="'success'"
>Paid</span>
</td>
<td
data-ui-align="end"
>$1,240.00</td>
</tr>
<tr>
<th
scope="row"
>
<ng-template
ui-text
[variant]="'code'"
>ORD-20932</ng-template>
<ng-template
ui-text
[tone]="'muted'"
[size]="'xs'"
>Placed 3 April</ng-template>
</th>
<td>Contoso</td>
<td>
<span
ui-badge
[tone]="'warning'"
>Due</span>
</td>
<td
data-ui-align="end"
>$86.50</td>
</tr>
<tr>
<th
scope="row"
>
<ng-template
ui-text
[variant]="'code'"
>ORD-20933</ng-template>
<ng-template
ui-text
[tone]="'muted'"
[size]="'xs'"
>Placed 3 April</ng-template>
</th>
<td>Fabrikam</td>
<td>
<span
ui-badge
>Draft</span>
</td>
<td
data-ui-align="end"
>$12,000.00</td>
</tr>
</tbody>
</table>
</div>
`,
})
export default class Example {}Compact, with controls
density="compact" sets shorter rows for long tables to scan. Every row is at least one control tall, so a row holding a checkbox or a small button keeps the rhythm of the rows around it.
| Name | Role | Seats | Actions |
|---|---|---|---|
| Owner | 3 | ||
| Editor | 1 | ||
| Viewer | 1 |
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-table/ui-table.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<ui-table
density="compact"
>
<table>
<caption>Team members</caption>
<thead>
<tr>
<th
scope="col"
><span
style="padding-inline-start: calc(1.125rem + var(--ui-space-2))"
>Name</span></th>
<th
scope="col"
>Role</th>
<th
scope="col"
data-ui-align="end"
>Seats</th>
<th
scope="col"
data-ui-align="center"
>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<ui-checkbox
checked
>Ada Lovelace</ui-checkbox>
</th>
<td>Owner</td>
<td
data-ui-align="end"
>3</td>
<td
data-ui-align="center"
>
<ui-button
size="sm"
variant="ghost"
aria-label="Edit Ada Lovelace"
>Edit</ui-button>
</td>
</tr>
<tr>
<th
scope="row"
>
<ui-checkbox>Grace Hopper</ui-checkbox>
</th>
<td>Editor</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<ui-button
size="sm"
variant="ghost"
aria-label="Edit Grace Hopper"
>Edit</ui-button>
</td>
</tr>
<tr>
<th
scope="row"
>
<ui-checkbox>Alan Turing</ui-checkbox>
</th>
<td>Viewer</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<ui-button
size="sm"
variant="ghost"
aria-label="Edit Alan Turing"
>Edit</ui-button>
</td>
</tr>
</tbody>
</table>
</ui-table><script setup>
import { UiButton, UiCheckbox, UiTable } from "@threadlabs/looma";
</script>
<template>
<UiTable
density="compact"
>
<table>
<caption>Team members</caption>
<thead>
<tr>
<th
scope="col"
><span
style="padding-inline-start: calc(1.125rem + var(--ui-space-2))"
>Name</span></th>
<th
scope="col"
>Role</th>
<th
scope="col"
data-ui-align="end"
>Seats</th>
<th
scope="col"
data-ui-align="center"
>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<UiCheckbox
:checked="true"
>Ada Lovelace</UiCheckbox>
</th>
<td>Owner</td>
<td
data-ui-align="end"
>3</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Ada Lovelace"
>Edit</UiButton>
</td>
</tr>
<tr>
<th
scope="row"
>
<UiCheckbox>Grace Hopper</UiCheckbox>
</th>
<td>Editor</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Grace Hopper"
>Edit</UiButton>
</td>
</tr>
<tr>
<th
scope="row"
>
<UiCheckbox>Alan Turing</UiCheckbox>
</th>
<td>Viewer</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Alan Turing"
>Edit</UiButton>
</td>
</tr>
</tbody>
</table>
</UiTable>
</template>import { UiButton, UiCheckbox, UiTable } from "@threadlabs/looma";
export default function Example() {
return <UiTable
density="compact"
>
<table>
<caption>Team members</caption>
<thead>
<tr>
<th
scope="col"
><span
style={{"paddingInlineStart":"calc(1.125rem + var(--ui-space-2))"}}
>Name</span></th>
<th
scope="col"
>Role</th>
<th
scope="col"
data-ui-align="end"
>Seats</th>
<th
scope="col"
data-ui-align="center"
>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<UiCheckbox
checked
>Ada Lovelace</UiCheckbox>
</th>
<td>Owner</td>
<td
data-ui-align="end"
>3</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Ada Lovelace"
>Edit</UiButton>
</td>
</tr>
<tr>
<th
scope="row"
>
<UiCheckbox>Grace Hopper</UiCheckbox>
</th>
<td>Editor</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Grace Hopper"
>Edit</UiButton>
</td>
</tr>
<tr>
<th
scope="row"
>
<UiCheckbox>Alan Turing</UiCheckbox>
</th>
<td>Viewer</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Alan Turing"
>Edit</UiButton>
</td>
</tr>
</tbody>
</table>
</UiTable>;
}/** @jsxImportSource preact */
import { UiButton, UiCheckbox, UiTable } from "@threadlabs/looma";
export default function Example() {
return <UiTable
density="compact"
>
<table>
<caption>Team members</caption>
<thead>
<tr>
<th
scope="col"
><span
style={{"paddingInlineStart":"calc(1.125rem + var(--ui-space-2))"}}
>Name</span></th>
<th
scope="col"
>Role</th>
<th
scope="col"
data-ui-align="end"
>Seats</th>
<th
scope="col"
data-ui-align="center"
>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<UiCheckbox
checked
>Ada Lovelace</UiCheckbox>
</th>
<td>Owner</td>
<td
data-ui-align="end"
>3</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Ada Lovelace"
>Edit</UiButton>
</td>
</tr>
<tr>
<th
scope="row"
>
<UiCheckbox>Grace Hopper</UiCheckbox>
</th>
<td>Editor</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Grace Hopper"
>Edit</UiButton>
</td>
</tr>
<tr>
<th
scope="row"
>
<UiCheckbox>Alan Turing</UiCheckbox>
</th>
<td>Viewer</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Alan Turing"
>Edit</UiButton>
</td>
</tr>
</tbody>
</table>
</UiTable>;
}<script>
import { UiButton, UiCheckbox, UiTable } from "@threadlabs/looma";
</script>
<UiTable
density="compact"
>
<table>
<caption>Team members</caption>
<thead>
<tr>
<th
scope="col"
><span
style="padding-inline-start: calc(1.125rem + var(--ui-space-2))"
>Name</span></th>
<th
scope="col"
>Role</th>
<th
scope="col"
data-ui-align="end"
>Seats</th>
<th
scope="col"
data-ui-align="center"
>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<UiCheckbox
checked
>Ada Lovelace</UiCheckbox>
</th>
<td>Owner</td>
<td
data-ui-align="end"
>3</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Ada Lovelace"
>Edit</UiButton>
</td>
</tr>
<tr>
<th
scope="row"
>
<UiCheckbox>Grace Hopper</UiCheckbox>
</th>
<td>Editor</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Grace Hopper"
>Edit</UiButton>
</td>
</tr>
<tr>
<th
scope="row"
>
<UiCheckbox>Alan Turing</UiCheckbox>
</th>
<td>Viewer</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<UiButton
size="sm"
variant="ghost"
aria-label="Edit Alan Turing"
>Edit</UiButton>
</td>
</tr>
</tbody>
</table>
</UiTable>import { Component } from "@angular/core";
import { UiButton, UiCheckbox, UiTable } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiTable, UiCheckbox, UiButton],
template: `
<div
ui-table
[density]="'compact'"
>
<table>
<caption>Team members</caption>
<thead>
<tr>
<th
scope="col"
><span
style="padding-inline-start: calc(1.125rem + var(--ui-space-2))"
>Name</span></th>
<th
scope="col"
>Role</th>
<th
scope="col"
data-ui-align="end"
>Seats</th>
<th
scope="col"
data-ui-align="center"
>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
<label
ui-checkbox
[checked]="true"
>Ada Lovelace</label>
</th>
<td>Owner</td>
<td
data-ui-align="end"
>3</td>
<td
data-ui-align="center"
>
<ng-template
ui-button
[size]="'sm'"
[variant]="'ghost'"
[hnAttributes]="{ 'aria-label': 'Edit Ada Lovelace' }"
>Edit</ng-template>
</td>
</tr>
<tr>
<th
scope="row"
>
<label
ui-checkbox
>Grace Hopper</label>
</th>
<td>Editor</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<ng-template
ui-button
[size]="'sm'"
[variant]="'ghost'"
[hnAttributes]="{ 'aria-label': 'Edit Grace Hopper' }"
>Edit</ng-template>
</td>
</tr>
<tr>
<th
scope="row"
>
<label
ui-checkbox
>Alan Turing</label>
</th>
<td>Viewer</td>
<td
data-ui-align="end"
>1</td>
<td
data-ui-align="center"
>
<ng-template
ui-button
[size]="'sm'"
[variant]="'ghost'"
[hnAttributes]="{ 'aria-label': 'Edit Alan Turing' }"
>Edit</ng-template>
</td>
</tr>
</tbody>
</table>
</div>
`,
})
export default class Example {}Sticky header
sticky-header keeps the header row in view while the rows scroll beneath it. The table scrolls inside its own box, so it needs a bounded height.
| Day | Parcels |
|---|---|
| Monday | 142 |
| Tuesday | 98 |
| Wednesday | 121 |
| Thursday | 87 |
| Friday | 163 |
| Saturday | 54 |
| Sunday | 12 |
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-table/ui-table.html"
>
<ui-table
sticky-header
style="block-size: 12rem"
>
<table>
<caption>Deliveries</caption>
<thead>
<tr>
<th
scope="col"
>Day</th>
<th
scope="col"
data-ui-align="end"
>Parcels</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Monday</th>
<td
data-ui-align="end"
>142</td>
</tr>
<tr>
<th
scope="row"
>Tuesday</th>
<td
data-ui-align="end"
>98</td>
</tr>
<tr>
<th
scope="row"
>Wednesday</th>
<td
data-ui-align="end"
>121</td>
</tr>
<tr>
<th
scope="row"
>Thursday</th>
<td
data-ui-align="end"
>87</td>
</tr>
<tr>
<th
scope="row"
>Friday</th>
<td
data-ui-align="end"
>163</td>
</tr>
<tr>
<th
scope="row"
>Saturday</th>
<td
data-ui-align="end"
>54</td>
</tr>
<tr>
<th
scope="row"
>Sunday</th>
<td
data-ui-align="end"
>12</td>
</tr>
</tbody>
</table>
</ui-table><script setup>
import { UiTable } from "@threadlabs/looma";
</script>
<template>
<UiTable
:sticky-header="true"
style="block-size: 12rem"
>
<table>
<caption>Deliveries</caption>
<thead>
<tr>
<th
scope="col"
>Day</th>
<th
scope="col"
data-ui-align="end"
>Parcels</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Monday</th>
<td
data-ui-align="end"
>142</td>
</tr>
<tr>
<th
scope="row"
>Tuesday</th>
<td
data-ui-align="end"
>98</td>
</tr>
<tr>
<th
scope="row"
>Wednesday</th>
<td
data-ui-align="end"
>121</td>
</tr>
<tr>
<th
scope="row"
>Thursday</th>
<td
data-ui-align="end"
>87</td>
</tr>
<tr>
<th
scope="row"
>Friday</th>
<td
data-ui-align="end"
>163</td>
</tr>
<tr>
<th
scope="row"
>Saturday</th>
<td
data-ui-align="end"
>54</td>
</tr>
<tr>
<th
scope="row"
>Sunday</th>
<td
data-ui-align="end"
>12</td>
</tr>
</tbody>
</table>
</UiTable>
</template>import { UiTable } from "@threadlabs/looma";
export default function Example() {
return <UiTable
stickyHeader
style={{"blockSize":"12rem"}}
>
<table>
<caption>Deliveries</caption>
<thead>
<tr>
<th
scope="col"
>Day</th>
<th
scope="col"
data-ui-align="end"
>Parcels</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Monday</th>
<td
data-ui-align="end"
>142</td>
</tr>
<tr>
<th
scope="row"
>Tuesday</th>
<td
data-ui-align="end"
>98</td>
</tr>
<tr>
<th
scope="row"
>Wednesday</th>
<td
data-ui-align="end"
>121</td>
</tr>
<tr>
<th
scope="row"
>Thursday</th>
<td
data-ui-align="end"
>87</td>
</tr>
<tr>
<th
scope="row"
>Friday</th>
<td
data-ui-align="end"
>163</td>
</tr>
<tr>
<th
scope="row"
>Saturday</th>
<td
data-ui-align="end"
>54</td>
</tr>
<tr>
<th
scope="row"
>Sunday</th>
<td
data-ui-align="end"
>12</td>
</tr>
</tbody>
</table>
</UiTable>;
}/** @jsxImportSource preact */
import { UiTable } from "@threadlabs/looma";
export default function Example() {
return <UiTable
stickyHeader
style={{"blockSize":"12rem"}}
>
<table>
<caption>Deliveries</caption>
<thead>
<tr>
<th
scope="col"
>Day</th>
<th
scope="col"
data-ui-align="end"
>Parcels</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Monday</th>
<td
data-ui-align="end"
>142</td>
</tr>
<tr>
<th
scope="row"
>Tuesday</th>
<td
data-ui-align="end"
>98</td>
</tr>
<tr>
<th
scope="row"
>Wednesday</th>
<td
data-ui-align="end"
>121</td>
</tr>
<tr>
<th
scope="row"
>Thursday</th>
<td
data-ui-align="end"
>87</td>
</tr>
<tr>
<th
scope="row"
>Friday</th>
<td
data-ui-align="end"
>163</td>
</tr>
<tr>
<th
scope="row"
>Saturday</th>
<td
data-ui-align="end"
>54</td>
</tr>
<tr>
<th
scope="row"
>Sunday</th>
<td
data-ui-align="end"
>12</td>
</tr>
</tbody>
</table>
</UiTable>;
}<script>
import { UiTable } from "@threadlabs/looma";
</script>
<UiTable
stickyHeader
style="block-size: 12rem"
>
<table>
<caption>Deliveries</caption>
<thead>
<tr>
<th
scope="col"
>Day</th>
<th
scope="col"
data-ui-align="end"
>Parcels</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Monday</th>
<td
data-ui-align="end"
>142</td>
</tr>
<tr>
<th
scope="row"
>Tuesday</th>
<td
data-ui-align="end"
>98</td>
</tr>
<tr>
<th
scope="row"
>Wednesday</th>
<td
data-ui-align="end"
>121</td>
</tr>
<tr>
<th
scope="row"
>Thursday</th>
<td
data-ui-align="end"
>87</td>
</tr>
<tr>
<th
scope="row"
>Friday</th>
<td
data-ui-align="end"
>163</td>
</tr>
<tr>
<th
scope="row"
>Saturday</th>
<td
data-ui-align="end"
>54</td>
</tr>
<tr>
<th
scope="row"
>Sunday</th>
<td
data-ui-align="end"
>12</td>
</tr>
</tbody>
</table>
</UiTable>import { Component } from "@angular/core";
import { UiTable } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiTable],
template: `
<div
ui-table
[stickyHeader]="true"
style="block-size: 12rem"
>
<table>
<caption>Deliveries</caption>
<thead>
<tr>
<th
scope="col"
>Day</th>
<th
scope="col"
data-ui-align="end"
>Parcels</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>Monday</th>
<td
data-ui-align="end"
>142</td>
</tr>
<tr>
<th
scope="row"
>Tuesday</th>
<td
data-ui-align="end"
>98</td>
</tr>
<tr>
<th
scope="row"
>Wednesday</th>
<td
data-ui-align="end"
>121</td>
</tr>
<tr>
<th
scope="row"
>Thursday</th>
<td
data-ui-align="end"
>87</td>
</tr>
<tr>
<th
scope="row"
>Friday</th>
<td
data-ui-align="end"
>163</td>
</tr>
<tr>
<th
scope="row"
>Saturday</th>
<td
data-ui-align="end"
>54</td>
</tr>
<tr>
<th
scope="row"
>Sunday</th>
<td
data-ui-align="end"
>12</td>
</tr>
</tbody>
</table>
</div>
`,
})
export default class Example {}Wider than its container
A table wider than its container scrolls sideways and fades the edge that hides columns. While it scrolls, the table is a region named by its caption that the keyboard can focus and scroll with the arrow keys.
| Region | Q1 | Q2 | Q3 | Q4 | Year |
|---|---|---|---|---|---|
| North | 1,204 | 1,388 | 1,522 | 1,610 | 5,724 |
| South | 986 | 1,040 | 1,115 | 1,302 | 4,443 |
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-table/ui-table.html"
>
<div
style="max-inline-size: 16rem"
>
<ui-table>
<table>
<caption>Quarterly figures</caption>
<thead>
<tr>
<th
scope="col"
>Region</th>
<th
scope="col"
data-ui-align="end"
>Q1</th>
<th
scope="col"
data-ui-align="end"
>Q2</th>
<th
scope="col"
data-ui-align="end"
>Q3</th>
<th
scope="col"
data-ui-align="end"
>Q4</th>
<th
scope="col"
data-ui-align="end"
>Year</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>North</th>
<td
data-ui-align="end"
>1,204</td>
<td
data-ui-align="end"
>1,388</td>
<td
data-ui-align="end"
>1,522</td>
<td
data-ui-align="end"
>1,610</td>
<td
data-ui-align="end"
>5,724</td>
</tr>
<tr>
<th
scope="row"
>South</th>
<td
data-ui-align="end"
>986</td>
<td
data-ui-align="end"
>1,040</td>
<td
data-ui-align="end"
>1,115</td>
<td
data-ui-align="end"
>1,302</td>
<td
data-ui-align="end"
>4,443</td>
</tr>
</tbody>
</table>
</ui-table>
</div><script setup>
import { UiTable } from "@threadlabs/looma";
</script>
<template>
<div
style="max-inline-size: 16rem"
>
<UiTable>
<table>
<caption>Quarterly figures</caption>
<thead>
<tr>
<th
scope="col"
>Region</th>
<th
scope="col"
data-ui-align="end"
>Q1</th>
<th
scope="col"
data-ui-align="end"
>Q2</th>
<th
scope="col"
data-ui-align="end"
>Q3</th>
<th
scope="col"
data-ui-align="end"
>Q4</th>
<th
scope="col"
data-ui-align="end"
>Year</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>North</th>
<td
data-ui-align="end"
>1,204</td>
<td
data-ui-align="end"
>1,388</td>
<td
data-ui-align="end"
>1,522</td>
<td
data-ui-align="end"
>1,610</td>
<td
data-ui-align="end"
>5,724</td>
</tr>
<tr>
<th
scope="row"
>South</th>
<td
data-ui-align="end"
>986</td>
<td
data-ui-align="end"
>1,040</td>
<td
data-ui-align="end"
>1,115</td>
<td
data-ui-align="end"
>1,302</td>
<td
data-ui-align="end"
>4,443</td>
</tr>
</tbody>
</table>
</UiTable>
</div>
</template>import { UiTable } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxInlineSize":"16rem"}}
>
<UiTable>
<table>
<caption>Quarterly figures</caption>
<thead>
<tr>
<th
scope="col"
>Region</th>
<th
scope="col"
data-ui-align="end"
>Q1</th>
<th
scope="col"
data-ui-align="end"
>Q2</th>
<th
scope="col"
data-ui-align="end"
>Q3</th>
<th
scope="col"
data-ui-align="end"
>Q4</th>
<th
scope="col"
data-ui-align="end"
>Year</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>North</th>
<td
data-ui-align="end"
>1,204</td>
<td
data-ui-align="end"
>1,388</td>
<td
data-ui-align="end"
>1,522</td>
<td
data-ui-align="end"
>1,610</td>
<td
data-ui-align="end"
>5,724</td>
</tr>
<tr>
<th
scope="row"
>South</th>
<td
data-ui-align="end"
>986</td>
<td
data-ui-align="end"
>1,040</td>
<td
data-ui-align="end"
>1,115</td>
<td
data-ui-align="end"
>1,302</td>
<td
data-ui-align="end"
>4,443</td>
</tr>
</tbody>
</table>
</UiTable>
</div>;
}/** @jsxImportSource preact */
import { UiTable } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxInlineSize":"16rem"}}
>
<UiTable>
<table>
<caption>Quarterly figures</caption>
<thead>
<tr>
<th
scope="col"
>Region</th>
<th
scope="col"
data-ui-align="end"
>Q1</th>
<th
scope="col"
data-ui-align="end"
>Q2</th>
<th
scope="col"
data-ui-align="end"
>Q3</th>
<th
scope="col"
data-ui-align="end"
>Q4</th>
<th
scope="col"
data-ui-align="end"
>Year</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>North</th>
<td
data-ui-align="end"
>1,204</td>
<td
data-ui-align="end"
>1,388</td>
<td
data-ui-align="end"
>1,522</td>
<td
data-ui-align="end"
>1,610</td>
<td
data-ui-align="end"
>5,724</td>
</tr>
<tr>
<th
scope="row"
>South</th>
<td
data-ui-align="end"
>986</td>
<td
data-ui-align="end"
>1,040</td>
<td
data-ui-align="end"
>1,115</td>
<td
data-ui-align="end"
>1,302</td>
<td
data-ui-align="end"
>4,443</td>
</tr>
</tbody>
</table>
</UiTable>
</div>;
}<script>
import { UiTable } from "@threadlabs/looma";
</script>
<div
style="max-inline-size: 16rem"
>
<UiTable>
<table>
<caption>Quarterly figures</caption>
<thead>
<tr>
<th
scope="col"
>Region</th>
<th
scope="col"
data-ui-align="end"
>Q1</th>
<th
scope="col"
data-ui-align="end"
>Q2</th>
<th
scope="col"
data-ui-align="end"
>Q3</th>
<th
scope="col"
data-ui-align="end"
>Q4</th>
<th
scope="col"
data-ui-align="end"
>Year</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>North</th>
<td
data-ui-align="end"
>1,204</td>
<td
data-ui-align="end"
>1,388</td>
<td
data-ui-align="end"
>1,522</td>
<td
data-ui-align="end"
>1,610</td>
<td
data-ui-align="end"
>5,724</td>
</tr>
<tr>
<th
scope="row"
>South</th>
<td
data-ui-align="end"
>986</td>
<td
data-ui-align="end"
>1,040</td>
<td
data-ui-align="end"
>1,115</td>
<td
data-ui-align="end"
>1,302</td>
<td
data-ui-align="end"
>4,443</td>
</tr>
</tbody>
</table>
</UiTable>
</div>import { Component } from "@angular/core";
import { UiTable } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiTable],
template: `
<div
style="max-inline-size: 16rem"
>
<div
ui-table
>
<table>
<caption>Quarterly figures</caption>
<thead>
<tr>
<th
scope="col"
>Region</th>
<th
scope="col"
data-ui-align="end"
>Q1</th>
<th
scope="col"
data-ui-align="end"
>Q2</th>
<th
scope="col"
data-ui-align="end"
>Q3</th>
<th
scope="col"
data-ui-align="end"
>Q4</th>
<th
scope="col"
data-ui-align="end"
>Year</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>North</th>
<td
data-ui-align="end"
>1,204</td>
<td
data-ui-align="end"
>1,388</td>
<td
data-ui-align="end"
>1,522</td>
<td
data-ui-align="end"
>1,610</td>
<td
data-ui-align="end"
>5,724</td>
</tr>
<tr>
<th
scope="row"
>South</th>
<td
data-ui-align="end"
>986</td>
<td
data-ui-align="end"
>1,040</td>
<td
data-ui-align="end"
>1,115</td>
<td
data-ui-align="end"
>1,302</td>
<td
data-ui-align="end"
>4,443</td>
</tr>
</tbody>
</table>
</div>
</div>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: div
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
density | How tall each row is. Every row is at least one control tall, so a row holding a control lines up with the rows around it. comfortable fits a control of the standard height (2.5rem), for tables read or edited at a glance; compact fits a small one (2rem), such as a Checkbox or a Button with size="sm", for long tables to scan. | density | keyword | comfortable | comfortable, compact |
sticky-header | Keeps the header row in view while the rows scroll beneath it. The table scrolls inside its own box, so this needs a bounded height, such as a table that fills a fixed-height panel. | stickyHeader | boolean | false | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
density | "comfortable" | "compact" | comfortable | comfortable, compact |
stickyHeader | boolean | false | — |
Events
No events.
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
No component tokens.
Shared tokens
| Token | Fallback |
|---|---|
--ui-border-divider | — |
--ui-border-subtle | — |
--ui-border-width | — |
--ui-control-size | — |
--ui-control-size-sm | — |
--ui-focus-ring | — |
--ui-focus-width | — |
--ui-font-medium | — |
--ui-font-size-sm | — |
--ui-line-height | — |
--ui-scroll-fade-size | 24px |
--ui-space-0-5 | — |
--ui-space-1 | — |
--ui-space-2 | — |
--ui-space-3 | — |
--ui-surface | — |
--ui-text-muted | — |
--ui-text-primary | — |
--ui-text-secondary | — |