A table displays data in rows and columns and enables a user to navigate its contents via directional navigation keys, and optionally supports row selection and sorting.
Name
Type
Date Modified
Games File folder 6/7/2020 Program Files File folder 4/7/2021 bootmgr System file 11/20/2010
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
export default function Demo () {
return (
< TableRoot aria-label = "Files" >
< TableHeader >
< TableColumn isRowHeader > Name </ TableColumn >
< TableColumn > Type </ TableColumn >
< TableColumn > Date Modified </ TableColumn >
</ TableHeader >
< TableBody >
< TableRow >
< TableCell > Games </ TableCell >
< TableCell > File folder </ TableCell >
< TableCell > 6/7/2020 </ TableCell >
</ TableRow >
< TableRow >
< TableCell > Program Files </ TableCell >
< TableCell > File folder </ TableCell >
< TableCell > 4/7/2021 </ TableCell >
</ TableRow >
< TableRow >
< TableCell > bootmgr </ TableCell >
< TableCell > System file </ TableCell >
< TableCell > 11/20/2010 </ TableCell >
</ TableRow >
</ TableBody >
</ TableRoot >
);
}
Installation
npx dotui-cli@latest add table
Anatomy
A table consists of a container element, with columns and rows of cells containing data inside. The cells within a table may contain focusable elements or plain text content.
import {
TableRoot,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
} from "@/components/core/table" ;
< TableRoot >
< TableHeader >
< TableColumn >#</ TableColumn >
< TableColumn >Name</ TableColumn >
< TableColumn >Email</ TableColumn >
</ TableHeader >
< TableBody >
< TableRow >
< TableCell >1</ TableCell >
< TableCell >Mehdi BHA</ TableCell >
< TableCell >hello@mehdibha.com</ TableCell >
</ TableRow >
< TableRow >
< TableCell >2</ TableCell >
< TableCell >Devon Govett</ TableCell >
< TableCell >devon@govett.com</ TableCell >
</ TableRow >
< TableRow >
< TableCell >3</ TableCell >
< TableCell >Theo Browne</ TableCell >
< TableCell >theo@ping.gg</ TableCell >
</ TableRow >
</ TableBody >
</ TableRoot >;
Variants
Use the variant
prop to change the appearance of the Table.
Name
Type
Date Modified
Games File folder 6/7/2020 Program Files File folder 4/7/2021 bootmgr System file 11/20/2010 log.txt Text Document 1/18/2016
"use client" ;
import React from "react" ;
import { Radio , RadioGroup } from "@/components/dynamic-core/radio-group" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/components/dynamic-core/table" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
type Variant = "solid" | "line" | "bordered" | "quiet" ;
export default function Demo () {
const [ variant , setVariant ] = React . useState < Variant >( "solid" );
return (
< div className = "flex gap-14" >
< TableRoot variant = { variant } aria-label = "Files" >
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
< RadioGroup
label = "Variant"
value = { variant }
onChange = { ( value ) => setVariant ( value as Variant ) }
>
< Radio value = "solid" > Solid </ Radio >
< Radio value = "bordered" > Bordered </ Radio >
< Radio value = "line" > Line </ Radio >
< Radio value = "quiet" > quiet </ Radio >
</ RadioGroup >
</ div >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Dynamic collections
The first example have shown static collections, where the data is hard coded. Dynamic collections, as shown below, can be used when the table data comes from an external data source such as an API, or updates over time. In the example below, both the columns and the rows are provided to the table via a render function. You can also make the columns static and only the rows dynamic.
Name
Type
Date Modified
Games File folder 6/7/2020 Program Files File folder 4/7/2021 bootmgr System file 11/20/2010 log.txt Text Document 1/18/2016
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
return (
< TableRoot aria-label = "Files" >
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Selection
Selection mode
By default, Table doesn't allow row selection but this can be enabled using the selectionMode
prop.
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
return (
< div className = "space-y-8" >
< TableRoot aria-label = "Files" selectionMode = "single" >
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
< TableRoot aria-label = "Files" selectionMode = "multiple" >
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
</ div >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Uncontrolled selection
Use defaultSelectedKeys
to provide a default set of selected rows. Note that the value of the selected keys must match the id
prop of the row.
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
return (
< TableRoot
aria-label = "Files"
selectionMode = "single"
defaultSelectedKeys = { [ 2 ] }
>
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Controlled selection
To programmatically control row selection, use the selectedKeys
prop paired with the onSelectionChange callback. The id
prop from the selected rows will be passed into the callback when the row is pressed, allowing you to update state accordingly.
"use client" ;
import React from "react" ;
import { type Selection } from "react-aria-components" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
const [ selectedKeys , setSelectedKeys ] = React . useState < Selection >(
new Set ([ 2 , 3 ])
);
return (
< TableRoot
aria-label = "Files"
selectionMode = "multiple"
selectedKeys = { selectedKeys }
onSelectionChange = { setSelectedKeys }
>
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Selection variant
Use the selectionVariant
prop to change the appearance of the selected rows.
"use client" ;
import React from "react" ;
import { RadioGroup , Radio } from "@/components/dynamic-core/radio-group" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/components/dynamic-core/table" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
type Variant = "primary" | "accent" ;
export default function Demo () {
const [ variant , setVariant ] = React . useState < Variant >( "primary" );
return (
< div className = "flex gap-12" >
< TableRoot
aria-label = "Files"
selectionMode = "single"
selectionVariant = { variant }
defaultSelectedKeys = { [ 2 ] }
>
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
< RadioGroup
label = "Variant"
value = { variant }
onChange = { ( value ) => setVariant ( value as Variant ) }
>
< Radio value = "primary" > Primary </ Radio >
< Radio value = "accent" > Accent </ Radio >
</ RadioGroup >
</ div >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Disallow empty selection
Table also supports a disallowEmptySelection
prop which forces the user to have at least one row in the Table selected at all times. In this mode, if a single row is selected and the user presses it, it will not be deselected.
disallow-empty-selection.tsx
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
return (
< TableRoot
aria-label = "Files"
selectionMode = "single"
defaultSelectedKeys = { [ 2 ] }
disallowEmptySelection
>
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Selection Behavior
You can control how multiple selection should behave in the collection using the selectionBehavior
prop.
"use client" ;
import React from "react" ;
import { RadioGroup , Radio } from "@/components/dynamic-core/radio-group" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/components/dynamic-core/table" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
type SelectionBehavior = "toggle" | "replace" ;
export default function Demo () {
const [ selectionBehavior , setSelectionBehavior ] =
React . useState < SelectionBehavior >( "toggle" );
return (
< div className = "flex gap-12" >
< TableRoot
aria-label = "Files"
selectionMode = "multiple"
selectionBehavior = { selectionBehavior }
defaultSelectedKeys = { [ 2 , 3 ] }
>
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
< RadioGroup
label = "Behavior"
value = { selectionBehavior }
onChange = { ( value ) => setSelectionBehavior ( value as SelectionBehavior ) }
className = "text-sm"
>
< Radio value = "toggle" > Toggle </ Radio >
< Radio value = "accent" > Replace </ Radio >
</ RadioGroup >
</ div >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Row options
Action
Table supports row actions via the onRowAction
prop
Name
Type
Date Modified
Games File folder 6/7/2020 Program Files File folder 4/7/2021 bootmgr System file 11/20/2010 log.txt Text Document 1/18/2016
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
return (
< TableRoot
aria-label = "Files"
onRowAction = { ( key ) => alert ( `Opening item ${ key } ...` ) }
>
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Rows may also have a row action specified by directly applying onAction
on the Row itself. This may be especially convenient in static collections.
Name
Type
Date Modified
Games File folder 6/7/2020 Program Files File folder 4/7/2021 bootmgr System file 11/20/2010
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
export default function Demo () {
return (
< TableRoot aria-label = "Files" >
< TableHeader >
< TableColumn isRowHeader > Name </ TableColumn >
< TableColumn > Type </ TableColumn >
< TableColumn > Date Modified </ TableColumn >
</ TableHeader >
< TableBody >
< TableRow onAction = { () => alert ( "Opening games" ) } >
< TableCell > Games </ TableCell >
< TableCell > File folder </ TableCell >
< TableCell > 6/7/2020 </ TableCell >
</ TableRow >
< TableRow onAction = { () => alert ( "Opening program files" ) } >
< TableCell > Program Files </ TableCell >
< TableCell > File folder </ TableCell >
< TableCell > 4/7/2021 </ TableCell >
</ TableRow >
< TableRow onAction = { () => alert ( "Opening bootmgr" ) } >
< TableCell > bootmgr </ TableCell >
< TableCell > System file </ TableCell >
< TableCell > 11/20/2010 </ TableCell >
</ TableRow >
</ TableBody >
</ TableRoot >
);
}
Link
Table rows may also be links to another page or website. This can be achieved by passing the href prop to the < TableRow >
component.
Name
URL
Date added
Adobe https://adobe.com/ January 28, 2023 Google https://google.com/ April 5, 2023 New York Times https://nytimes.com/ July 12, 2023
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
return (
< TableRoot aria-label = "Files" >
< TableHeader columns = { columns } >
< TableColumn isRowHeader > Name </ TableColumn >
< TableColumn > URL </ TableColumn >
< TableColumn > Date added </ TableColumn >
</ TableHeader >
< TableBody items = { data } >
< TableRow href = "https://adobe.com/" target = "_blank" >
< TableCell > Adobe </ TableCell >
< TableCell > https://adobe.com/ </ TableCell >
< TableCell > January 28, 2023 </ TableCell >
</ TableRow >
< TableRow href = "https://google.com/" target = "_blank" >
< TableCell > Google </ TableCell >
< TableCell > https://google.com/ </ TableCell >
< TableCell > April 5, 2023 </ TableCell >
</ TableRow >
< TableRow href = "https://nytimes.com/" target = "_blank" >
< TableCell > New York Times </ TableCell >
< TableCell > https://nytimes.com/ </ TableCell >
< TableCell > July 12, 2023 </ TableCell >
</ TableRow >
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Disabled
A Row can be disabled with the isDisabled
prop.
< TableRow isDisabled >
< TableCell >1</ TableCell >
< TableCell >Mehdi BHA</ TableCell >
< TableCell >hello@mehdibha.com</ TableCell >
</ TableRow >
In dynamic collections, it may be more convenient to use the disabledKeys
prop at the Table level instead of isDisabled
on individual rows.
Name
Type
Date Modified
Games File folder 6/7/2020 Program Files File folder 4/7/2021 bootmgr System file 11/20/2010 log.txt Text Document 1/18/2016
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const data : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
return (
< TableRoot aria-label = "Files" disabledKeys = { [ 3 , 4 ] } >
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { data } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Sorting
Table supports sorting its data when a column header is pressed. To designate that a Column should support sorting, provide it with the allowsSorting
prop.
The Table accepts a sortDescriptor
prop that defines the current column key to sort by and the sort direction (ascending/descending). When the user presses a sortable column header, the column's key and sort direction is passed into the onSortChange
callback, allowing you to update the sortDescriptor
appropriately.
Date Modified
bootmgr System file 11/20/2010 Games File folder 6/7/2020 log.txt Text Document 1/18/2016 Program Files File folder 4/7/2021
"use client" ;
import React from "react" ;
import type { SortDescriptor } from "react-aria-components" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const items : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
const [ sortDescriptor , setSortDescriptor ] = React . useState < SortDescriptor >({
column: "name" ,
direction: "ascending" ,
});
const sortedItems = React . useMemo (() => {
return items . sort (( a , b ) => {
const first = a [ sortDescriptor . column as keyof Item ] as string ;
const second = b [ sortDescriptor . column as keyof Item ] as string ;
let cmp = first . localeCompare ( second );
if ( sortDescriptor . direction === "descending" ) {
cmp *= - 1 ;
}
return cmp ;
});
}, [ sortDescriptor ]);
return (
< TableRoot
aria-label = "Files"
sortDescriptor = { sortDescriptor }
onSortChange = { setSortDescriptor }
>
< TableHeader columns = { columns } >
< TableColumn id = "name" isRowHeader allowsSorting >
Name
</ TableColumn >
< TableColumn id = "type" allowsSorting >
Type
</ TableColumn >
< TableColumn id = "date" > Date Modified </ TableColumn >
</ TableHeader >
< TableBody items = { sortedItems } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Empty state
Use the renderEmptyState
prop to customize what the TableBody
will display if there are no items.
By default the TableBody
will display a message saying "No results found."
.
Name
Type
Date Modified
No results found.
Name
Type
Date Modified
Nothing here.
"use client" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableColumn ,
} from "@/registry/core/table_basic" ;
export default function Demo () {
return (
< div className = "flex gap-8" >
< TableRoot aria-label = "Files" >
< TableHeader >
< TableColumn id = "name" isRowHeader >
Name
</ TableColumn >
< TableColumn id = "type" > Type </ TableColumn >
< TableColumn id = "date" > Date Modified </ TableColumn >
</ TableHeader >
< TableBody > { [] } </ TableBody >
</ TableRoot >
< TableRoot aria-label = "Files" >
< TableHeader >
< TableColumn id = "name" isRowHeader >
Name
</ TableColumn >
< TableColumn id = "type" > Type </ TableColumn >
< TableColumn id = "date" > Date Modified </ TableColumn >
</ TableHeader >
< TableBody renderEmptyState = { () => "Nothing here." } > { [] } </ TableBody >
</ TableRoot >
</ div >
);
}
Column resizing
Date Modified
Games File folder 6/7/2020 Program Files File folder 4/7/2021 bootmgr System file 11/20/2010 log.txt Text Document 1/18/2016
"use client" ;
import React from "react" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
TableContainer ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
const items : Item [] = [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
];
export default function Demo () {
return (
< TableContainer resizable >
< TableRoot aria-label = "Files" >
< TableHeader columns = { columns } >
< TableColumn id = "name" isRowHeader allowsResizing >
Name
</ TableColumn >
< TableColumn id = "type" allowsResizing >
Type
</ TableColumn >
< TableColumn id = "date" > Date Modified </ TableColumn >
</ TableHeader >
< TableBody items = { items } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
</ TableContainer >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
Drag and drop
Reordable
Name
Type
Date Modified
Games File folder 6/7/2020 Program Files File folder 4/7/2021 bootmgr System file 11/20/2010 log.txt Text Document 1/18/2016
"use client" ;
import { useDragAndDrop } from "react-aria-components" ;
import { useListData } from "react-stately" ;
import {
TableRoot ,
TableHeader ,
TableBody ,
TableRow ,
TableColumn ,
TableCell ,
} from "@/registry/core/table_basic" ;
const columns : Column [] = [
{ name: "Name" , id: "name" , isRowHeader: true },
{ name: "Type" , id: "type" },
{ name: "Date Modified" , id: "date" },
];
export default function Demo () {
const list = useListData < Item >({
initialItems: [
{ id: 1 , name: "Games" , date: "6/7/2020" , type: "File folder" },
{ id: 2 , name: "Program Files" , date: "4/7/2021" , type: "File folder" },
{ id: 3 , name: "bootmgr" , date: "11/20/2010" , type: "System file" },
{ id: 4 , name: "log.txt" , date: "1/18/2016" , type: "Text Document" },
],
});
const { dragAndDropHooks } = useDragAndDrop ({
getItems : ( keys ) =>
[ ... keys ]. map (( key ) => {
const item = list . getItem ( key );
return {
"text/plain" : item ?. name ?? "" ,
};
}),
onReorder ( e ) {
if ( e . target . dropPosition === "before" ) {
list . moveBefore ( e . target . key , e . keys );
} else if ( e . target . dropPosition === "after" ) {
list . moveAfter ( e . target . key , e . keys );
}
},
});
return (
< TableRoot aria-label = "Files" dragAndDropHooks = { dragAndDropHooks } >
< TableHeader columns = { columns } >
{ ( column ) => (
< TableColumn isRowHeader = { column . isRowHeader } >
{ column . name }
</ TableColumn >
) }
</ TableHeader >
< TableBody items = { list . items } >
{ ( item ) => (
< TableRow columns = { columns } >
{ ( column ) => < TableCell > { item [ column . id ] } </ TableCell > }
</ TableRow >
) }
</ TableBody >
</ TableRoot >
);
}
type Item = {
id : number ;
name : string ;
date : string ;
type : string ;
};
interface Column {
id : keyof Omit < Item , "id" >;
name : string ;
isRowHeader ?: boolean ;
}
API Reference
TableRoot
Prop Type Default Description children
ReactNode
- The elements that make up the table. Includes the TableHeader, TableBody, Columns, and Rows. selectionBehavior
'toggle' | 'replace'
'toggle'
How multiple selection should behave in the collection. disabledBehavior
'selection' | 'all'
'selectio n
Whether disabledKeys applies to all interactions, or only selection. dragAndDropHooks
DragAndDropHooks
- The drag and drop hooks returned by useDragAndDrop used to enable drag and drop behavior for the Table. disabledKeys
Iterable < Key >
- A list of row keys to disable. selectionMode
'none' | 'single' | 'multiple'
- The type of selection that is allowed in the collection. disallowEmptySelection
boolean
- Whether the collection allows empty selection. selectedKeys
'all' | Iterable < Key >
- The currently selected keys in the collection (controlled). defaultSelectedKeys
'all' | Iterable < Key >
- The initial selected keys in the collection (uncontrolled). sortDescriptor
SortDescriptor
- The current sorted column and direction. className
string | ( values : TableRenderProps & { defaultClassName : string | undefined }) => string
- The CSS className for the element. A function may be provided to compute the class based on component state. style
CSSProperties | ( values : TableRenderProps & { defaultStyle : CSSProperties }) => CSSProperties
- The inline style for the element. A function may be provided to compute the style based on component state.
Event Type Description onRowAction
( key : Key ) => void
Handler that is called when a user performs an action on the row. onSelectionChange
( key : Selection ) => void
Handler that is called when the selection changes. onSortChange
( descriptor : SortDescriptor ) => any
Handler that is called when the sorted column or direction changes. onScroll
( e : UIEvent < Element >) => void
Handler that is called when a user scrolls. See MDN .