--- title: Table description: A responsive table component for presenting data inline. component: true source: shadcn: true --- Table is an opinionated `` element for basic tabular data presentation. It is usually presented within a [Card](../components/card). ## Usage ```tsx import { Card, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, } from 'ui' ``` ```tsx
Column 1 Column 2 Column 3 Value 1 Value 2 Value 3 Total Value
``` ## Layout Columns will naturally take the width of their content unless otherwise specified. Specific column widths may be useful for controlling layout shift or for aligning multiple Table instances vertically. Table Head includes a `whitespace-nowrap` utility class to prevent its text from wrapping. Table Cell must have any custom width specified, as there are legitimate use cases for both wrapping (or truncated) text. The Table component is wrapped in a `ShadowScrollArea` by default, which provides horizontal scrolling on smaller screens when the table content exceeds the viewport width. ## Structure Table is comprised of the following [Shadcn primitives](https://ui.shadcn.com/docs/components/table) which map directly to their HTML counterparts: | Component | HTML element | | ------------- | ------------------------------------------------------------------------------------------- | | Table | [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/table) | | Table Body | [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/tbody) | | Table Caption | [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/tfoot) | | Table Head | [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/thead) | | Table Row | [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/tr) | Table and its child components follow their HTML element counterparts for semantics, accessibility, and best practices. ### Table Caption Table Caption describes a table’s purpose or content. Think of it like the label of a diagram. It should only contain a single text node. Just like its `
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/caption) | | Table Cell | [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/td) | | Table Footer | [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/th) | | Table Header | [`
` counterpart, Table Caption should be the first child of a Table element (before a Table Body) irrespective of its final visual positioning. ### Table Footer Table Footer is a semantic grouping for the final rows of a table, typically used for column summaries, totals, or footnotes that relate to the data in the body of the table. Table Footer typically contains the same child elements as a Table Body, such as Table Row and Table Cell, and is a sibling of Table Body. ## Examples ### Empty state When displaying an empty state within a table, dim the Table Head text to indicate that no data is present. Use consistent zero results presentation across your application to maintain a cohesive user experience. To prevent the empty state row from being highlighted on hover, apply the `[&>td]:hover:bg-inherit` class to the Table Row containing the empty state message. ```tsx showLineNumbers {1}

No results found

Your search for “test” did not return any results

``` See [Empty States](../ui-patterns/empty-states) for more information. ### Sortable columns Use `TableHeadSort` inside Table Head to enable column sorting. This component provides visual indicators for sort state (ascending, descending, or unsorted) and handles click interactions. ```tsx import { TableHeadSort } from 'ui' ``` | Prop | Type | Description | | -------------- | -------------------------- | ------------------------------------------------------------------ | | `column` | `string` | Unique identifier for the column | | `currentSort` | `string` | Current sort state in format `"column:order"` (e.g., `"name:asc"`) | | `onSortChange` | `(column: string) => void` | Callback fired when column header is clicked | | `children` | `ReactNode` | The label text for the column header | | `className` | `string` | Optional additional CSS classes | ```tsx showLineNumbers {2} Name ``` The component displays: - An up arrow when the column is sorted ascending - A down arrow when the column is sorted descending - A chevrons icon when the column is not currently sorted (visible on hover) For TanStack tables, prefer the shared adapter instead of reimplementing the `TableHeadSort` bridge in each table. ```tsx import { TanStackTableHeadSort } from 'ui-patterns/Table' ``` ```tsx showLineNumbers const columns: ColumnDef[] = [ { accessorKey: 'name', header: ({ column }) => Name, }, ] ``` This keeps TanStack tables aligned with the same `TableHeadSort` visual treatment and sorting cycle used by manual tables. ### Row icons When adding icon columns to your table, use [Accessibility](../accessibility) markup by including a screen reader-only label in the corresponding Table Head using the `sr-only` class. This ensures that assistive technologies can properly identify the column's purpose. Remove these icon cells when loading or displaying zero results to maintain a clean and consistent table structure. ```tsx showLineNumbers {4, 5, 13, 14} Icon Name Email Value Value
``` ### Actions Action should be placed in the last column of each Table Row to maintain a logical reading flow. Users scan table data from left to right, so positioning actions on the right ensures they encounter the primary information first before reaching interactive controls. For compact controls in action cells, add the `hit-area-2` utility to increase the tap target by `8px` on each side without changing visual layout. When actions sit next to each other, keep at least `gap-x-2` spacing: with two adjacent `hit-area-2` buttons, hit areas meet at the midpoint but do not overlap. #### Multiple actions When multiple actions are available for a row, display one primary action as a button and place additional options in an overflow menu. This keeps the table clean and scannable while providing access to all necessary actions without overwhelming the user. #### Cross-links You may need to link to related resources from within table cells. In these cases, use the `text-link-table-cell` CSS class to provide a visual hint that the text is interactive while maintaining its appearance as tertiary content. Avoid making the entire row interactive when using cross-links. Multiple interactive areas within a single row increases the chance of mis-taps and creates ambiguity about which action will be triggered. Keep cross-links as discrete, scannable elements within their respective cells, and maintain a dedicated action column for primary row-level actions. #### Row-level navigation The entire Table Row may be tappable. This pattern is most effective when navigation is the sole or primary action for each row, as it provides a large, easy-to-target interaction area. Avoid adding other actions when using row-level navigation, as multiple interactive areas within a single row create competing affordances and increase interaction complexity. When implementing row-level navigation, pay close attention to [Accessibility](/accessibility#focus-management) requirements. The row must be keyboard accessible with proper focus management. Also consider these affordances: - Handle `Enter` and `Space` key presses for activation - Provide visual focus indicators using classes like `focus-inset` - Support modifier keys (`Ctrl`/`Cmd`, middle-click) for opening links in new tabs - Consider using the shared `createNavigationHandler` function to handle modifier keys - Avoid bubbling up action events from _within_ the row #### Row navigation with actions If you must combine row-level navigation with additional actions, nest all secondary actions in an overflow menu and maintain a [ChevronRight](https://lucide.dev/icons/chevron-right) visual indicator to signal that the row is navigable. This pattern preserves the primary navigation affordance while keeping secondary actions accessible but unobtrusive. This hybrid approach requires careful attention to event handling. Ensure that taps on action buttons stop event propagation to prevent triggering row navigation, and maintain clear visual separation between the navigable row area and the action controls. ## Related Use the Table component for simple, static tabular data presentation with a fixed number of rows. Consider using [Data Table](../ui-patterns/tables#data-table) for more complex cases as it provides built-in sorting, filtering, and pagination capabilities. Refer to [Tables](../ui-patterns/tables) for broader guidance and best practices on presenting tabular data.