Skip to main content

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
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
2
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
3
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
4
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
5
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
6
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive

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 install 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
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
2
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
3
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
4
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
5
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
6
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
// ... 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
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
2
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
3
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
4
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
5
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
6
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
// ... 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
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
2
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
3
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
4
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
5
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
6
John Doe

Jon Brown

@fredchaparro

jhondoe@aegonui.comActive
// ... 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.

PropsTypeDescriptionDefault
emptyTextReactNodeSet empty text, it will only appear when table has no data__
variantTableVariantsThe variants of the component are:"modern"
stripedbooleanAdd striping style__
classNamestringAdd custom classes for extra style__
idstring__
styleCSSProperties__
titlePanelRender<DefaultRecordType>__
captionReactNode__
datareadonly DefaultRecordType[]__
footerPanelRender<DefaultRecordType>__
summary((data: readonly DefaultRecordType[]) => ReactNode)__
prefixClsstring__
directionltr rtl__
expandedRowKeysKey[]__
defaultExpandedRowKeyskey[]__
expandedRowRenderExpandedRowRender<DefaultRecordType>__
expandRowByClickboolean__
expandIconRenderExpandIcon<DefaultRecordType>__
onExpand((expanded: boolean, record: DefaultRecordType) => void)__
onExpandedRowsChange((expandedKeys: Key[]) => void)__
defaultExpandAllRowsboolean__
indentSizenumber__
expandIconColumnIndexnumber__
expandedRowClassNameRowClassName<DefaultRecordType>__
childrenColumnNamestring__
columnsColumnsType<DefaultRecordType>__
rowKeystring GetRowKey<DefaultRecordType>__
tableLayoutfixed auto__
scrollTableScroll__
expandableExpandableConfig<DefaultRecordType>Config expand rows__
rowClassNamestring RowClassName<DefaultRecordType>__
showHeaderboolean__
componentsTableComponents<DefaultRecordType>__
onRowGetComponentProps<DefaultRecordType>__
onHeaderRowGetComponentProps<readonly ColumnType<DefaultRecordType>[]>__
internalHooksstring__
transformColumnsTableTransformColumns__
internalRefs{ body: MutableRefObject<HTMLDivElement>; }__
stickysticky tableSticky__

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>;