Table
A table displays data in a tabular format. It is used to organize information and make it easier to read and understand.
Name | Access | |
---|---|---|
John Doe | john.doe@example.com | Owner |
Jane Smith | jane.smith@example.com | Member |
Alice Johnson | alice.johnson@example.com | Admin |
Bob Williams | bob.williams@example.com | Member |
Charlie Brown | charlie.brown@example.com | Admin |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'cwl-ui'
export function Default() {
return (
<Table>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>John Doe</TableCell>
<TableCell>john.doe@example.com</TableCell>
<TableCell>Owner</TableCell>
</TableRow>
<TableRow>
<TableCell>Jane Smith</TableCell>
<TableCell>jane.smith@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Alice Johnson</TableCell>
<TableCell>alice.johnson@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow>
<TableCell>Bob Williams</TableCell>
<TableCell>bob.williams@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Charlie Brown</TableCell>
<TableCell>charlie.brown@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
Component API
Prop | Default | Description |
---|---|---|
bleed | false | Whether the table should bleed into the gutter. |
dense | false | Whether the table uses condensed spacing. |
grid | false | Whether to display vertical grid lines |
striped | false | Whether to display striped rows |
This is a custom Table component that does not use react-aria-components like other components in this library.
Examples
Responsive Table
Tables automatically overflow horizontally when the content is wider than the container.
Name | Handle | Role | Access | |
---|---|---|---|---|
John Doe | @johndoe | Co-Founder / CEO | john.doe@example.com | Admin |
Jane Smith | @janesmith | Co-Founder / CTO | jane.smith@example.com | Owner |
Alice Johnson | @alicejohnson | Business Relations | alice.johnson@example.com | Member |
Bob Williams | @bobwilliams | Front-end Developer | bob.williams@example.com | Member |
Charlie Brown | @charliebrown | Designer | charlie.brown@example.com | Admin |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'cwl-ui'
export function Overflow() {
return (
<Table>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Handle</TableHeader>
<TableHeader>Role</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>John Doe</TableCell>
<TableCell>@johndoe</TableCell>
<TableCell>Co-Founder / CEO</TableCell>
<TableCell>john.doe@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow>
<TableCell>Jane Smith</TableCell>
<TableCell>@janesmith</TableCell>
<TableCell>Co-Founder / CTO</TableCell>
<TableCell>jane.smith@example.com</TableCell>
<TableCell>Owner</TableCell>
</TableRow>
<TableRow>
<TableCell>Alice Johnson</TableCell>
<TableCell>@alicejohnson</TableCell>
<TableCell>Business Relations</TableCell>
<TableCell>alice.johnson@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Bob Williams</TableCell>
<TableCell>@bobwilliams</TableCell>
<TableCell>Front-end Developer</TableCell>
<TableCell>bob.williams@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Charlie Brown</TableCell>
<TableCell>@charliebrown</TableCell>
<TableCell>Designer</TableCell>
<TableCell>charlie.brown@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
Full Width Table
Use the bleed
prop to make the table full width.
Name | Access | |
---|---|---|
John Doe | john.doe@example.com | Owner |
Jane Smith | jane.smith@example.com | Member |
Alice Johnson | alice.johnson@example.com | Admin |
Bob Williams | bob.williams@example.com | Member |
Charlie Brown | charlie.brown@example.com | Admin |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'cwl-ui'
export function Bleed() {
return (
<Table bleed>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>John Doe</TableCell>
<TableCell>john.doe@example.com</TableCell>
<TableCell>Owner</TableCell>
</TableRow>
<TableRow>
<TableCell>Jane Smith</TableCell>
<TableCell>jane.smith@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Alice Johnson</TableCell>
<TableCell>alice.johnson@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow>
<TableCell>Bob Williams</TableCell>
<TableCell>bob.williams@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Charlie Brown</TableCell>
<TableCell>charlie.brown@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
Rows as links
Use the href
props to make the rows clickable.
Name | Access | |
---|---|---|
John Doe | john.doe@example.com | Owner |
Jane Smith | jane.smith@example.com | Member |
Alice Johnson | alice.johnson@example.com | Admin |
Bob Williams | bob.williams@example.com | Member |
Charlie Brown | charlie.brown@example.com | Admin |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'cwl-ui'
export function Links() {
return (
<Table>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow href="#johndoe">
<TableCell>John Doe</TableCell>
<TableCell>john.doe@example.com</TableCell>
<TableCell>Owner</TableCell>
</TableRow>
<TableRow href="#janesmith">
<TableCell>Jane Smith</TableCell>
<TableCell>jane.smith@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow href="#alicejohnson">
<TableCell>Alice Johnson</TableCell>
<TableCell>alice.johnson@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow href="#bobwilliams">
<TableCell>Bob Williams</TableCell>
<TableCell>bob.williams@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow href="#charliebrown">
<TableCell>Charlie Brown</TableCell>
<TableCell>charlie.brown@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
With condensed spacing
Use the dense
prop to make the table use condensed spacing.
Rank | Player | Pos | GP | G | A | P | +/- |
---|---|---|---|---|---|---|---|
1 | John Doe | R | 80 | 30 | 69 | 99 | +18 |
2 | Jane Smith | R | 82 | 40 | 47 | 87 | +10 |
3 | Alice Johnson | C | 74 | 40 | 45 | 85 | +31 |
4 | Bob Williams | C | 80 | 36 | 44 | 80 | -7 |
5 | Charlie Brown | L | 82 | 23 | 26 | 49 | +21 |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'cwl-ui'
export function Dense() {
return (
<div className="w-[500px]">
<Table dense>
<TableHead>
<TableRow>
<TableHeader>Rank</TableHeader>
<TableHeader>Player</TableHeader>
<TableHeader>Pos</TableHeader>
<TableHeader>GP</TableHeader>
<TableHeader>G</TableHeader>
<TableHeader>A</TableHeader>
<TableHeader>P</TableHeader>
<TableHeader>+/-</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>1</TableCell>
<TableCell>John Doe</TableCell>
<TableCell>R</TableCell>
<TableCell>80</TableCell>
<TableCell>30</TableCell>
<TableCell>69</TableCell>
<TableCell>99</TableCell>
<TableCell>+18</TableCell>
</TableRow>
<TableRow>
<TableCell>2</TableCell>
<TableCell>Jane Smith</TableCell>
<TableCell>R</TableCell>
<TableCell>82</TableCell>
<TableCell>40</TableCell>
<TableCell>47</TableCell>
<TableCell>87</TableCell>
<TableCell>+10</TableCell>
</TableRow>
<TableRow>
<TableCell>3</TableCell>
<TableCell>Alice Johnson</TableCell>
<TableCell>C</TableCell>
<TableCell>74</TableCell>
<TableCell>40</TableCell>
<TableCell>45</TableCell>
<TableCell>85</TableCell>
<TableCell>+31</TableCell>
</TableRow>
<TableRow>
<TableCell>4</TableCell>
<TableCell>Bob Williams</TableCell>
<TableCell>C</TableCell>
<TableCell>80</TableCell>
<TableCell>36</TableCell>
<TableCell>44</TableCell>
<TableCell>80</TableCell>
<TableCell>-7</TableCell>
</TableRow>
<TableRow>
<TableCell>5</TableCell>
<TableCell>Charlie Brown</TableCell>
<TableCell>L</TableCell>
<TableCell>82</TableCell>
<TableCell>23</TableCell>
<TableCell>26</TableCell>
<TableCell>49</TableCell>
<TableCell>+21</TableCell>
</TableRow>
{/* Add more rows as needed */}
</TableBody>
</Table>
</div>
)
}
With grid lines
Use the grid
prop to make the table display vertical grid lines.
Name | Access | |
---|---|---|
John Doe | john.doe@example.com | Owner |
Jane Smith | jane.smith@example.com | Member |
Alice Johnson | alice.johnson@example.com | Admin |
Bob Williams | bob.williams@example.com | Member |
Charlie Brown | charlie.brown@example.com | Admin |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'cwl-ui'
export function Grid() {
return (
<Table grid>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>John Doe</TableCell>
<TableCell>john.doe@example.com</TableCell>
<TableCell>Owner</TableCell>
</TableRow>
<TableRow>
<TableCell>Jane Smith</TableCell>
<TableCell>jane.smith@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Alice Johnson</TableCell>
<TableCell>alice.johnson@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow>
<TableCell>Bob Williams</TableCell>
<TableCell>bob.williams@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Charlie Brown</TableCell>
<TableCell>charlie.brown@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
With striped lines
Use the striped
prop to make the table display striped rows.
Name | Access | |
---|---|---|
John Doe | john.doe@example.com | Owner |
Jane Smith | jane.smith@example.com | Member |
Alice Johnson | alice.johnson@example.com | Admin |
Bob Williams | bob.williams@example.com | Member |
Charlie Brown | charlie.brown@example.com | Admin |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'cwl-ui'
export function Striped() {
return (
<Table striped>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>John Doe</TableCell>
<TableCell>john.doe@example.com</TableCell>
<TableCell>Owner</TableCell>
</TableRow>
<TableRow>
<TableCell>Jane Smith</TableCell>
<TableCell>jane.smith@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Alice Johnson</TableCell>
<TableCell>alice.johnson@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow>
<TableCell>Bob Williams</TableCell>
<TableCell>bob.williams@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow>
<TableCell>Charlie Brown</TableCell>
<TableCell>charlie.brown@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
With striped links
Use the striped
and the href
prop in combination with one another.
Name | Access | |
---|---|---|
John Doe | john.doe@example.com | Owner |
Jane Smith | jane.smith@example.com | Member |
Alice Johnson | alice.johnson@example.com | Admin |
Bob Williams | bob.williams@example.com | Member |
Charlie Brown | charlie.brown@example.com | Admin |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'cwl-ui'
export function StripedLinks() {
return (
<Table striped>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Email</TableHeader>
<TableHeader>Access</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow href="#johndoe">
<TableCell>John Doe</TableCell>
<TableCell>john.doe@example.com</TableCell>
<TableCell>Owner</TableCell>
</TableRow>
<TableRow href="#janesmith">
<TableCell>Jane Smith</TableCell>
<TableCell>jane.smith@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow href="#alicejohnson">
<TableCell>Alice Johnson</TableCell>
<TableCell>alice.johnson@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow href="#bobwilliams">
<TableCell>Bob Williams</TableCell>
<TableCell>bob.williams@example.com</TableCell>
<TableCell>Member</TableCell>
</TableRow>
<TableRow href="#charliebrown">
<TableCell>Charlie Brown</TableCell>
<TableCell>charlie.brown@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
</TableBody>
</Table>
)
}
Installation
To install, copy the code below and paste into your project.
'use client'
import { createContext, useContext, useState } from 'react'
import { Link } from 'react-aria-components'
import { cn } from './lib/utils'
interface TableProps extends React.ComponentPropsWithoutRef<'table'> {
bleed?: boolean
dense?: boolean
grid?: boolean
striped?: boolean
}
const TableContext = createContext<TableProps>({
bleed: false,
dense: false,
grid: false,
striped: false,
})
export const Table = ({ children, bleed, dense, grid, striped, ...props }: TableProps) => {
return (
<TableContext.Provider
value={{
bleed,
dense,
grid,
striped,
}}
>
<div className="overflow-x-auto whitespace-nowrap">
<table className="min-w-full text-left text-sm/6" {...props}>
{children}
</table>
</div>
</TableContext.Provider>
)
}
export const TableHead = ({ children, ...props }: React.ComponentPropsWithoutRef<'thead'>) => {
return (
<thead className="text-zinc-500" {...props}>
{children}
</thead>
)
}
export const TableBody = ({ ...props }: React.ComponentPropsWithoutRef<'tbody'>) => {
return <tbody {...props} />
}
const TableRowContext = createContext<{
href?: string
target?: string
title?: string
}>({
href: undefined,
target: undefined,
title: undefined,
})
interface TableRowProps extends React.ComponentPropsWithoutRef<'tr'> {
href?: string
target?: string
title?: string
}
export const TableRow = ({ children, href, target, title, className, ...props }: TableRowProps) => {
const { striped } = useContext(TableContext)
return (
<TableRowContext.Provider value={{ href, title, target }}>
<tr
{...props}
className={cn(
className,
href &&
'relative rounded-md has-[[data-row-link]]:cursor-pointer has-[[data-row-link][data-focused]]:outline has-[[data-row-link][data-focused]]:outline-2 has-[[data-row-link][data-focused]]:-outline-offset-2 has-[[data-row-link][data-focused]]:outline-blue-500',
striped && 'even:bg-zinc-950/[2.5%]',
href && striped && 'hover:bg-zinc-950/5',
href && !striped && 'hover:bg-zinc-950/[2.5%]',
)}
>
{children}
</tr>
</TableRowContext.Provider>
)
}
export const TableHeader = ({
className,
children,
...props
}: React.ComponentPropsWithoutRef<'th'>) => {
const { bleed, grid } = useContext(TableContext)
return (
<th
className={cn(
className,
'border-b border-b-zinc-950/10 px-4 font-medium first:pl-2 last:pr-2',
grid && 'border-l border-l-zinc-950/5 first:border-l-0 first:pl-0',
!bleed && 'sm:first:pl-2 sm:last:pr-2',
)}
{...props}
>
{children}
</th>
)
}
export const TableCell = ({
className,
children,
...props
}: React.ComponentPropsWithoutRef<'td'>) => {
const { bleed, dense, grid, striped } = useContext(TableContext)
const { href, target, title } = useContext(TableRowContext)
const [cellRef, setCellRef] = useState<HTMLElement | null>(null)
return (
<td
ref={href ? setCellRef : undefined}
{...props}
className={cn(
className,
'px-4 tabular-nums first:pl-2 last:pr-2',
!striped && 'border-b border-zinc-950/5',
grid && 'border-l border-l-zinc-950/5 first:border-l-0',
dense ? 'py-2.5' : 'py-4',
!bleed && 'sm:first:pl-2 sm:last:pr-2',
)}
>
{cellRef?.previousElementSibling === null && href && (
// Since the href attribute is passed in at the TableRow level we need a way to make sure only the first
// TableCell is rendered as an anchor.
// We then use the data attributes exposed by react-aria on the TableRow level to make the entire row look
// Focusable when in reality it is just the first row. That is why we've got focus:outline-none set below.
<Link
data-row-link
href={href}
target={target}
aria-label={title}
className="absolute inset-0 focus:outline-none"
/>
)}
{children}
</td>
)
}