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