Rc Table
A powerful, flexible table component built on top of rc-table. Seamlessly integrated with RizzUI's design system, this component provides a comprehensive solution for building data tables with sorting, filtering, and multiple visual variants.
Id | Employee | Email | Status | ||
|---|---|---|---|---|---|
| 1 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 2 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 3 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 4 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 5 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 6 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active |
Features
- 🎨 Multiple Variants - Modern, Minimal, and Elegant styles
- ✅ Row Selection - Select all/deselect all with indeterminate state support
- 🔍 Sorting Support - Built-in column sorting functionality
- 📊 Flexible Styling - Customizable with RizzUI theme variables
- 🎯 TypeScript Support - Full type safety out of the box
- 🌓 Dark Mode Ready - Automatic dark mode styling
- 📱 Responsive - Works seamlessly across all device sizes
- ⚡ Performant - Optimized for large datasets
- 🎛️ Striped Rows - Optional striped row styling
- ♿ Accessible - Built with accessibility best practices
Installation
Before using the Rc Table component, you'll need to install the required dependency:
Step 1
Install the rc-table package.
- npm
- yarn
- pnpm
- bun
npm install rc-table
yarn add rc-table
pnpm add rc-table
bun add rc-table
Step 2
Create a table component, components/table.tsx
import React from 'react';
import RcTable from 'rc-table';
import { tv, type VariantProps } from 'tailwind-variants';
import { Empty, cn } from 'rizzui';
export type ExtractProps<T> = T extends React.ComponentType<infer P> ? P : T;
const tableStyles = tv({
base: 'rizzui-rc-table [&_.rc-table-content]:overflow-x-auto [&_table]:w-full [&_.rc-table-row:hover]:bg-muted/60',
variants: {
variant: {
modern:
'[&_thead]:bg-muted/50 [&_td.rc-table-cell]:border-b [&_td.rc-table-cell]:border-border]',
minimal: '[&_thead]:bg-muted/50]',
elegant:
'[&_thead]:border-y [&_thead]:border-border [&_td.rc-table-cell]:border-b [&_td.rc-table-cell]:border-border]',
},
striped: {
true: '[&_.rc-table-row:nth-child(2n)_.rc-table-cell]:bg-muted/40]',
},
},
defaultVariants: {
variant: 'modern',
striped: false,
},
});
const theadStyles = tv({
base: '[&_thead]:text-left [&_thead]:rtl:text-right [&_th.rc-table-cell]:uppercase [&_th.rc-table-cell]:text-xs [&_th.rc-table-cell]:font-medium [&_th.rc-table-cell]:tracking-wide',
});
const tCellStyles = tv({
base: '[&_.rc-table-cell]:px-3 [&_th.rc-table-cell]:py-3 [&_td.rc-table-cell]:py-4]',
});
type RCTableProps = ExtractProps<typeof RcTable>;
export interface TableProps
extends Omit<RCTableProps, 'className' | 'emptyText'> {
emptyText?: React.ReactElement;
variant?: VariantProps<typeof tableStyles>['variant'];
striped?: boolean;
className?: string;
}
export default function Table({
striped,
variant = 'modern',
emptyText,
className,
...props
}: TableProps) {
return (
<RcTable
className={cn(
tableStyles({ variant, striped }),
theadStyles(),
tCellStyles(),
className
)}
emptyText={emptyText ?? <Empty />}
{...props}
/>
);
}
Table.displayName = 'Table';
Step 3
Create a header-cell.tsx component for table header cells.
import React from 'react';
import { cn } from 'rizzui';
type TextAlign = 'left' | 'center' | 'right';
export interface HeaderCellProps {
title: React.ReactNode;
width?: number;
align?: TextAlign;
ellipsis?: boolean;
sortable?: boolean;
ascending?: boolean;
iconClassName?: string;
className?: string;
}
function handleTextAlignment(align: TextAlign) {
if (align === 'center') return 'justify-center';
if (align === 'right') return 'justify-end rtl:justify-start';
return '';
}
export default function HeaderCell({
title,
align = 'left',
width,
ellipsis,
sortable,
ascending,
iconClassName,
className,
}: HeaderCellProps) {
if (ellipsis && width === undefined) {
console.warn(
'When ellipsis is true make sure you are using the same column width in HeaderCell component too.'
);
}
if (width !== undefined && ellipsis !== true) {
console.warn(
"width prop without ellipsis won't work, please set ellipsis prop true."
);
}
return (
<div
className={cn(
'flex items-center gap-1',
sortable && 'cursor-pointer',
handleTextAlignment(align),
className
)}
>
<div
{...(ellipsis && { className: 'truncate' })}
{...(ellipsis && width && { style: { width } })}
>
{title}
</div>
{sortable && (
<div className="inline-flex">
{ascending ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
className={cn('h-auto w-3', iconClassName)}
viewBox="0 0 16 16"
>
<path d="m7.247 4.86-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
className={cn('h-auto w-3', iconClassName)}
viewBox="0 0 16 16"
>
<path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z" />
</svg>
)}
</div>
)}
</div>
);
}
HeaderCell.displayName = 'HeaderCell';
Modern
You can change the style of Table by changing property variant.
Id | Employee | Email | Status | ||
|---|---|---|---|---|---|
| 1 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 2 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 3 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 4 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 5 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 6 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active |
// ... same setup as above ...
export default function App() {
// ... same state and columns setup ...
return (
<div className="w-full overflow-x-auto">
<Table
data={data}
columns={columns}
variant="modern"
className="text-sm"
/>
</div>
);
}
Elegant
You can change the style of Table by changing property variant.
Id | Employee | Email | Status | ||
|---|---|---|---|---|---|
| 1 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 2 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 3 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 4 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 5 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 6 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active |
// ... same setup as above ...
export default function App() {
// ... same state and columns setup ...
return (
<div className="w-full overflow-x-auto">
<Table
data={data}
columns={columns}
variant="elegant"
className="text-sm"
/>
</div>
);
}
Minimal
You can change the style of Table by changing property variant.
Id | Employee | Email | Status | ||
|---|---|---|---|---|---|
| 1 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 2 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 3 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 4 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 5 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active | ||
| 6 | Jon Brown @fredchaparro | jhondoe@aegonui.com | Active |
// ... same setup as above ...
export default function App() {
// ... same state and columns setup ...
return (
<div className="w-full overflow-x-auto">
<Table
data={data}
columns={columns}
variant="minimal"
className="text-sm"
/>
</div>
);
}
Advanced Usage
Striped Rows
Add striped styling to alternate rows:
<Table
data={data}
columns={columns}
variant="modern"
striped
className="text-sm"
/>
Custom Empty State
Customize the empty state when there's no data:
import { Empty } from 'rizzui/empty';
<Table
data={[]}
columns={columns}
emptyText={<Empty text="No data available" className="py-12" />}
/>;
Row Selection
Enable row selection with select all functionality:
const [selectedRows, setSelectedRows] = React.useState<Set<string>>(new Set());
const handleSelectAll = React.useCallback(
(checked: boolean) => {
if (checked) {
setSelectedRows(new Set(data.map((row) => row.id)));
} else {
setSelectedRows(new Set());
}
},
[data]
);
const handleSelectRow = React.useCallback((id: string, checked: boolean) => {
setSelectedRows((prev) => {
const newSet = new Set(prev);
if (checked) {
newSet.add(id);
} else {
newSet.delete(id);
}
return newSet;
});
}, []);
const getColumns = (
order: string,
column: string,
onHeaderClick: (value: string) => any,
selectedRows: Set<string>,
onSelectAll: (checked: boolean) => void,
onSelectRow: (id: string, checked: boolean) => void,
dataLength: number
) => {
const allSelected = selectedRows.size > 0 && selectedRows.size === dataLength;
const someSelected = selectedRows.size > 0 && selectedRows.size < dataLength;
return [
{
title: (
<div className="inline-flex cursor-pointer">
<Checkbox
variant="flat"
checked={allSelected}
indeterminate={someSelected}
onChange={(e) => onSelectAll(e.target.checked)}
aria-label="Select all rows"
/>
</div>
),
dataIndex: 'checked',
key: 'checked',
width: 50,
render: (_: any, row: any) => (
<div className="inline-flex cursor-pointer">
<Checkbox
variant="flat"
checked={selectedRows.has(row.id)}
onChange={(e) => onSelectRow(row.id, e.target.checked)}
aria-label={`Select row ${row.id}`}
/>
</div>
),
},
// ... other columns
];
};
Column Sorting
Enable sorting on columns using the HeaderCell component:
{
title: (
<HeaderCell
title="Name"
sortable
ascending={order === "asc" && column === "name"}
/>
),
onHeaderCell: () => onHeaderClick("name"),
dataIndex: "name",
key: "name",
}
Best Practices
- Use memoization - Memoize columns and data to prevent unnecessary re-renders
- Column widths - Set appropriate column widths for better layout control
- Accessibility - Ensure proper ARIA labels and keyboard navigation
- Performance - Use React.useMemo for columns and data when needed
- Type safety - Use TypeScript for type-safe column definitions
- Theme consistency - Use RizzUI theme variables for consistent styling
- Responsive design - Wrap tables in overflow containers for mobile devices
API Reference
Table Props
Here is the API documentation of the Table component.
| Props | Type | Description | Default |
|---|---|---|---|
| emptyText | ReactNode | Set empty text, it will only appear when table has no data | __ |
| variant | TableVariants | The variants of the component are: | "modern" |
| striped | boolean | Add striping style | __ |
| className | string | Add custom classes for extra style | __ |
| id | string | __ | |
| style | CSSProperties | __ | |
| title | PanelRender<DefaultRecordType> | __ | |
| caption | ReactNode | __ | |
| data | readonly DefaultRecordType[] | __ | |
| footer | PanelRender<DefaultRecordType> | __ | |
| summary | ((data: readonly DefaultRecordType[]) => ReactNode) | __ | |
| prefixCls | string | __ | |
| direction | ltr rtl | __ | |
| expandedRowKeys | Key[] | __ | |
| defaultExpandedRowKeys | key[] | __ | |
| expandedRowRender | ExpandedRowRender<DefaultRecordType> | __ | |
| expandRowByClick | boolean | __ | |
| expandIcon | RenderExpandIcon<DefaultRecordType> | __ | |
| onExpand | ((expanded: boolean, record: DefaultRecordType) => void) | __ | |
| onExpandedRowsChange | ((expandedKeys: Key[]) => void) | __ | |
| defaultExpandAllRows | boolean | __ | |
| indentSize | number | __ | |
| expandIconColumnIndex | number | __ | |
| expandedRowClassName | RowClassName<DefaultRecordType> | __ | |
| childrenColumnName | string | __ | |
| columns | ColumnsType<DefaultRecordType> | __ | |
| rowKey | string GetRowKey<DefaultRecordType> | __ | |
| tableLayout | fixed auto | __ | |
| scroll | TableScroll | __ | |
| expandable | ExpandableConfig<DefaultRecordType> | Config expand rows | __ |
| rowClassName | string RowClassName<DefaultRecordType> | __ | |
| showHeader | boolean | __ | |
| components | TableComponents<DefaultRecordType> | __ | |
| onRow | GetComponentProps<DefaultRecordType> | __ | |
| onHeaderRow | GetComponentProps<readonly ColumnType<DefaultRecordType>[]> | __ | |
| internalHooks | string | __ | |
| transformColumns | TableTransformColumns | __ | |
| internalRefs | { body: MutableRefObject<HTMLDivElement>; } | __ | |
| sticky | sticky tableSticky | __ |
- Note: You can check rc-table documentation for more details.
Table Variants
type TableVariants = 'modern' | 'minimal' | 'elegant';
Table Scroll
type TableScroll =
| { x?: string | number | true; y?: string | number }
| undefined;
Table Transform Columns
type TableTransformColumns = (
columns: ColumnsType<DefaultRecordType>
) => ColumnsType<DefaultRecordType>;