Table
If you can put it in a database, you can put it in a table.
| Name | Role | |
|---|---|---|
| Leslie Alexander | leslie.alexander@example.com | Admin |
| Michael Foster | michael.foster@example.com | Owner |
| Dries Vincent | dries.vincent@example.com | Member |
| Lindsay Walton | lindsay.walton@example.com | Member |
| Courtney Henry | courtney.henry@example.com | Admin |
<script>
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Role</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> Component API
| Prop | Default | Description |
|---|---|---|
Table extends the JSX <table> element | ||
bleed | false | Whether the table should bleed into the gutter. |
dense | false | Whether the table should use condensed spacing. |
grid | false | Whether display vertical grid lines. |
striped | false | Whether display striped table rows. |
TableHead extends the JSX <thead> element | ||
| This component does not expose any component-specific props. | ||
TableBody extends the JSX <tbody> element | ||
| This component does not expose any component-specific props. | ||
TableRow extends the JSX <tr> element | ||
href | - | The URL for the row when used as a link. |
target | - | The target for the row when used as a link. |
title | - | The title for the row whe used as a link. |
TableHeader extends the JSX <th> element | ||
| This component does not expose any component-specific props. | ||
TableCell extends the JSX <td> element | ||
| This component does not expose any component-specific props. | ||
Examples
Basic example
Use the Table, TableHead, TableBody, TableRow, TableHeader, and TableCell components to build a table:
| Name | Role | |
|---|---|---|
| Leslie Alexander | leslie.alexander@example.com | Admin |
| Michael Foster | michael.foster@example.com | Owner |
| Dries Vincent | dries.vincent@example.com | Member |
| Lindsay Walton | lindsay.walton@example.com | Member |
| Courtney Henry | courtney.henry@example.com | Admin |
<script>
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Role</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> Responsive tables
Tables automatically become scrollable when they are wider than their container:
| Name | Handle | Role | Access | |
|---|---|---|---|---|
| Leslie Alexander | @@lesliealexander | Co-Founder / CEO | leslie.alexander@example.com | Admin |
| Michael Foster | @@michaelfoster | Co-Founder / CTO | michael.foster@example.com | Owner |
| Dries Vincent | @@driesvincent | Business Relations | dries.vincent@example.com | Member |
| Lindsay Walton | @@lindsaywalton | Front-end Developer | lindsay.walton@example.com | Member |
| Courtney Henry | @@courtneyhenry | Designer | courtney.henry@example.com | Admin |
<script>
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Handle</TableHeader>
<TableHeader>Role</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>@{user.handle}</TableCell>
<TableCell>{user.role}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> Set the CSS --gutter variable to match the padding of the containing element to make sure the table isn't cropped unnecessarily when it becomes scrollable. You can change the gutter responsively using media query variants, such as sm:[--gutter:--spacing(4)].
Full-width tables
Use the bleed prop and set the CSS --gutter variable to match the padding of the containing element to make a table full-width:
| Name | Role | |
|---|---|---|
| Leslie Alexander | leslie.alexander@example.com | Admin |
| Michael Foster | michael.foster@example.com | Owner |
| Dries Vincent | dries.vincent@example.com | Member |
| Lindsay Walton | lindsay.walton@example.com | Member |
| Courtney Henry | courtney.henry@example.com | Admin |
<script>
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table bleed class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Role</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> Full-width tables are still responsive and will become scrollable if they don't fit within the containing element.
Rows as links
Use the href prop on the TableRow component to treat an entire row like a link:
<script>
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Role</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow href={user.url}>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> When used as a link, TableRow also accepts the target and title props like a regular link.
With condensed spacing
Use the dense prop to render the table with condensed spacing:
| Rank | Player | Pos | GP | G | A | P | +/- |
|---|---|---|---|---|---|---|---|
| 1 | Michell Marner | R | 80 | 30 | 69 | 99 | +18 |
| 2 | William Nylander | R | 82 | 40 | 47 | 87 | +10 |
| 3 | Auston Matthews | C | 74 | 40 | 45 | 85 | +31 |
| 4 | John Tavares | C | 80 | 36 | 44 | 80 | +7 |
| 5 | Michael Bunting | L | 82 | 23 | 26 | 49 | +21 |
| 6 | Morgan Rielly | D | 65 | 4 | 37 | 41 | -9 |
| 7 | Calle Jarnkrok | C | 73 | 20 | 19 | 39 | -9 |
| 8 | Alex Kerfoot | C | 82 | 10 | 22 | 32 | +8 |
| 9 | David Kampf | C | 82 | 7 | 20 | 27 | +6 |
| 10 | Mark Giordano | D | 78 | 4 | 20 | 24 | +27 |
<script>
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table dense class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow>
<TableHeader>Rank</TableHeader>
<TableHeader>Player</TableHeader>
<TableHeader class="text-right">Pos</TableHeader>
<TableHeader class="text-right">GP</TableHeader>
<TableHeader class="text-right">G</TableHeader>
<TableHeader class="text-right">A</TableHeader>
<TableHeader class="text-right">P</TableHeader>
<TableHeader class="text-right">+/-</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each players as player (player.rank)}
<TableRow key={player.rank}>
<TableCell class="tabular-nums">{player.rank}</TableCell>
<TableCell class="font-medium">{player.name}</TableCell>
<TableCell class="text-right">{player.position}</TableCell>
<TableCell class="text-right tabular-nums">{player.gamesPlayed}</TableCell>
<TableCell class="text-right tabular-nums">{player.goals}</TableCell>
<TableCell class="text-right tabular-nums">{player.assists}</TableCell>
<TableCell class="text-right tabular-nums">{player.points}</TableCell>
<TableCell class="text-right tabular-nums">{player.plusMinus}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> With grid lines
Use the grid prop to render the table with vertical grid lines:
| Name | Role | |
|---|---|---|
| Leslie Alexander | leslie.alexander@example.com | Admin |
| Michael Foster | michael.foster@example.com | Owner |
| Dries Vincent | dries.vincent@example.com | Member |
| Lindsay Walton | lindsay.walton@example.com | Member |
| Courtney Henry | courtney.henry@example.com | Admin |
<script>
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table grid class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Role</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> With striped rows
Use the striped prop to render the table with striped rows and no horizontal borders:
| Name | Role | |
|---|---|---|
| Leslie Alexander | leslie.alexander@example.com | Admin |
| Michael Foster | michael.foster@example.com | Owner |
| Dries Vincent | dries.vincent@example.com | Member |
| Lindsay Walton | lindsay.walton@example.com | Member |
| Courtney Henry | courtney.henry@example.com | Admin |
import {(Table, TableBody, TableCell, TableHead, TableHeader, TableRow)} from '$lib/components/table';
<Table striped class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Role</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> With different heading color
Use the text-{color} utilities on the TableRow component inside your TableHead to change the color of table headings:
| Name | Role | |
|---|---|---|
| Leslie Alexander | leslie.alexander@example.com | Admin |
| Michael Foster | michael.foster@example.com | Owner |
| Dries Vincent | dries.vincent@example.com | Member |
| Lindsay Walton | lindsay.walton@example.com | Member |
| Courtney Henry | courtney.henry@example.com | Admin |
<script>
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow class="text-zinc-950 dark:text-white">
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Role</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> With complex content
Tables are unopinionated about their content and will adapt to just about anything you include:
| Name | Role | Status |
|---|---|---|
| Leslie Alexander | Admin | Online |
| Michael Foster | Owner | Online |
| Dries Vincent | Member | Offline |
| Lindsay Walton | Member | Online |
| Courtney Henry | Admin | Online |
<script>
import { Avatar } from '$lib/components/avatar';
import { Badge } from '$lib/components/badge';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<Table class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Role</TableHeader>
<TableHeader>Status</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell>
<div class="flex items-center gap-4">
<Avatar src={user.avatarUrl} class="size-12" />
<div>
<div class="font-medium">{user.name}</div>
<div class="text-zinc-500">
<a href="#" class="hover:text-zinc-700">
{user.email}
</a>
</div>
</div>
</div>
</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
<TableCell>
{#if user.online}
<Badge color="lime">Online</Badge>
{:else}
<Badge color="zinc">Offline</Badge>
{/if}
</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> With pagination
Add a Pagination component below your table to add pagination controls:
Users
| Name | Access | |
|---|---|---|
| Leslie Alexander | leslie.alexander@example.com | Admin |
| Michael Foster | michael.foster@example.com | Owner |
| Dries Vincent | dries.vincent@example.com | Member |
| Lindsay Walton | lindsay.walton@example.com | Member |
| Courtney Henry | courtney.henry@example.com | Admin |
<script>
import {
Pagination,
PaginationGap,
PaginationList,
PaginationNext,
PaginationPage,
PaginationPrevious
} from '$lib/components/pagination';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
</script>
<h1 class="mb-6 text-base font-semibold">Users</h1>
<Table>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table>
<Pagination class="mt-6">
<PaginationPrevious href="?page=2" />
<PaginationList>
<PaginationPage href="?page=1">1</PaginationPage>
<PaginationPage href="?page=2">2</PaginationPage>
<PaginationPage href="?page=3" current>3</PaginationPage>
<PaginationPage href="?page=4">4</PaginationPage>
<PaginationGap />
<PaginationPage href="?page=65">65</PaginationPage>
<PaginationPage href="?page=66">66</PaginationPage>
</PaginationList>
<PaginationNext href="?page=4" />
</Pagination> Use the mt-* utilities to control the space between the table and the pagination controls.
With dropdowns
Use the Dropdown component within a TableCell to add a dropdown menu:
| Name | Access | Actions | |
|---|---|---|---|
| Leslie Alexander | leslie.alexander@example.com | Admin | |
| Michael Foster | michael.foster@example.com | Owner | |
| Dries Vincent | dries.vincent@example.com | Member | |
| Lindsay Walton | lindsay.walton@example.com | Member | |
| Courtney Henry | courtney.henry@example.com | Admin | |
<script>
import { Dropdown, DropdownButton, DropdownItem, DropdownMenu } from '$lib/components/dropdown';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/table';
import HeroiconsEllipsisHorizontal16Solid from 'virtual:icons/heroicons/ellipsis-horizontal-16-solid';
</script>
<Table class="[--gutter:--spacing(6)] sm:[--gutter:--spacing(8)]">
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
<TableHeader class="relative w-0">
<span class="sr-only">Actions</span>
</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{#each users as user (user.handle)}
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
<TableCell>
<div class="-mx-3 -my-1.5 sm:-mx-2.5">
<Dropdown>
<DropdownButton plain aria-label="More options">
<HeroiconsEllipsisHorizontal16Solid />
</DropdownButton>
<DropdownMenu anchor="bottom end">
<DropdownItem>View</DropdownItem>
<DropdownItem>Edit</DropdownItem>
<DropdownItem>Delete</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</TableCell>
</TableRow>
{/each}
</TableBody>
</Table> When adding elements like dropdowns to a table (especially with the plain style), consider using negative margins to avoid increasing the size of the table cell. For instance, in the example above we've added -my-1.5 to make sure the dropdown only takes up 24px of vertical space in the actual layout, which matches the height of the text in the other cells.
In dialog
Add a Table to your DialogBody component to include a table in a dialog:
<script>
import { Button } from '$lib/components/button';
import {
Dialog,
DialogActions,
DialogBody,
DialogDescription,
DialogTitle
} from '$lib/components/dialog';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/table';
</script>
<Button type="button" onclick={() => isOpen = true}>
Show users
</Button>
<Dialog open={isOpen} onclose={() => isOpen = false} size="3xl">
<DialogTitle>Users</DialogTitle>
<DialogDescription>The follow users have access to your account.</DialogDescription>
<DialogBody>
<Table bleed compact>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Role</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => (
<TableRow>
<TableCell class="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell class="text-zinc-500">{user.access}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</DialogBody>
<DialogActions>
<Button onclick={() => isOpen = false}>Close</Button>
</DialogActions>
</Dialog> When using tables within dialogs, the --gutter variable is automatically set to match the dialog's padding.