Table

If you can put it in a database, you can put it in a table.

Name Email 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

PropDefaultDescription
Table extends the JSX <table> element
bleedfalseWhether the table should bleed into the gutter.
densefalseWhether the table should use condensed spacing.
gridfalseWhether display vertical grid lines.
stripedfalseWhether 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 Email 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 Email 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 Email 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.

Use the href prop on the TableRow component to treat an entire row like a link:

Name Email 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>
			<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 Email 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 Email 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 Email 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
Admin Online
Owner Online
Member Offline
Member Online
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 Email 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 Email 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.