Themes & Color Mode
react-data-table-component ships with a set of built-in structural themes and a flexible theming system built on CSS custom properties. Pick a named theme with the theme prop, brand any theme with createTheme(), and let colorMode handle light/dark automatically.
Built-in themes
Select a theme and color mode. Structural themes define density, spacing, and shape. colorMode controls light/dark.
import DataTable from 'react-data-table-component';
<DataTable columns={columns} data={data} />
<DataTable columns={columns} data={data} theme="material" />
<DataTable columns={columns} data={data} theme="crisp" colorMode="dark" />
<DataTable columns={columns} data={data} theme="catppuccin" colorMode="system" />Structural themes
Structural themes define density, spacing, typography, and shape. They ship with a neutral palette that adapts to light and dark mode via colorMode. Brand them with createTheme() to add your own accent color:
// Apply Material Design layout with your own brand color
createTheme('material-violet', { primary: '#6200EE' }, 'material');
<DataTable columns={columns} data={data} theme="material-violet" />;| Name | Density | Shape | Separator defaults |
|---|---|---|---|
default | 48 px rows, 16 px padding | 4 px radius | Header separator on, no column separators |
material | 52 px rows, 16 px padding | 4 px radius | No header or column separators (Material uses row dividers only) |
rounded | 48 px rows, 18 px padding | 8 px radius, circular checkboxes | Subtle header separator |
catppuccin | 48 px rows, 16 px padding | 6 px radius | Subtle header separator, Catppuccin Latte (light) / Mocha (dark) |
crisp | 42 px rows, 16 px padding | 4 px radius | Subtle header separator (AG Grid Quartz) |
All themes include a darkMode block and respond correctly tocolorMode="system" or colorMode="dark".
colorMode
The colorMode prop controls whether DataTable applies the active theme'sdarkMode overrides.
| Value | Behavior |
|---|---|
"light" | Always render in light mode regardless of OS preference or page class. |
"dark" | Always render in dark mode, applying the theme's darkMode overrides. |
"system" (default) | Follow the page automatically. DataTable checks, in order:
MutationObserver and astorage event listener, so DataTable updates when the user toggles the page theme without needing a re-render from outside. |
// Always dark — ignores OS preference
<DataTable columns={columns} data={data} theme="material" colorMode="dark" />
// Follow OS / page toggle (default, explicit)
<DataTable columns={columns} data={data} theme="crisp" colorMode="system" />
// Force light even when the page is in dark mode
<DataTable columns={columns} data={data} theme="rounded" colorMode="light" />createTheme()
createTheme() accepts two call signatures:
import { createTheme } from 'react-data-table-component';
// 1. Named — register globally, reference by string in the theme prop
createTheme('my-brand', { primary: '#6200EE' }, 'material');
// 2. Inline — compose and pass the object directly to the theme prop
const inlineTheme = createTheme({ primary: '#e91e63' }, 'crisp');
<DataTable theme={inlineTheme} columns={columns} data={data} />;Theme shape
All keys are optional. Unspecified fields inherit from the base theme. FordarkMode, supply only the fields that differ from light mode; they are merged on top of the light values at render time.
import { createTheme } from 'react-data-table-component';
createTheme('violet', {
primary: '#6200EE',
text: {
primary: '#1a1a2e',
secondary: '#4a4a6a',
disabled: '#9e9e9e',
},
background: {
default: '#f5f0ff',
header: '#ede7f6', // distinct header background
},
divider: { default: '#d1c4e9' },
selected: { default: '#ede7f6', text: '#1a1a2e' },
highlightOnHover: { default: '#ede7f6', text: '#1a1a2e' },
striped: { default: '#f3eeff', text: '#1a1a2e' },
button: {
default: '#6200EE',
focus: 'rgba(98,0,238,0.12)',
hover: 'rgba(98,0,238,0.08)',
disabled: '#d1c4e9',
},
context: { background: '#6200EE', text: '#ffffff' },
// Dark-mode overrides — only what changes
darkMode: {
primary: '#BB86FC',
text: { primary: '#e0e0e0', secondary: '#b0b0b0' },
background: { default: '#1a0533', header: '#240844' },
divider: { default: '#3d1f6e' },
selected: { default: '#2d1060', text: '#e0e0e0' },
highlightOnHover: { default: '#2d1060', text: '#e0e0e0' },
striped: { default: '#1e0a40', text: '#e0e0e0' },
},
// Structural
spacing: { rowHeight: '48px', headerHeight: '56px', cellPaddingX: '16px' },
typography: { fontSize: '14px', fontSizeHeader: '12px' },
shape: { borderRadius: '8px' },
});Icons
Sort chevron, row expander, and pagination arrows can all be replaced. There are two ways: bake them into a theme so every table using it gets them automatically, or pass them as per-table props for a one-off override. Props win when both are set.
Custom icons
Switch between the built-in icons and custom inline-SVG replacements for pagination, expander, and sort.
import { useState } from 'react';
import DataTable from '../ThemedDataTable';
import { type TableColumn } from 'react-data-table-component';
// Inline SVG icon primitives — no external dependency
const ChevronLeft = () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="15 18 9 12 15 6" />
</svg>
);
const ChevronRight = () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="9 18 15 12 9 6" />
</svg>
);
const ChevronsLeft = () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="11 17 6 12 11 7" />
<polyline points="18 17 13 12 18 7" />
</svg>
);
const ChevronsRight = () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="13 17 18 12 13 7" />
<polyline points="6 17 11 12 6 7" />
</svg>
);
const ChevronDown = () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="6 9 12 15 18 9" />
</svg>
);
const ChevronRightSmall = () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="9 18 15 12 9 6" />
</svg>
);
const ArrowUp = () => (
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="12" y1="19" x2="12" y2="5" />
<polyline points="5 12 12 5 19 12" />
</svg>
);
interface Employee {
id: number;
name: string;
role: string;
department: string;
bio: string;
}
const data: Employee[] = [
{
id: 1,
name: 'Aria Chen',
role: 'Engineering Lead',
department: 'Engineering',
bio: 'Leads the platform team with a focus on reliability.',
},
{
id: 2,
name: 'Marcus Webb',
role: 'Product Manager',
department: 'Product',
bio: 'Drives roadmap strategy across three product lines.',
},
{
id: 3,
name: 'Priya Kapoor',
role: 'Senior Designer',
department: 'Design',
bio: 'Owns the design system and mobile experience.',
},
{
id: 4,
name: 'Jordan Ellis',
role: 'Data Scientist',
department: 'Analytics',
bio: 'Builds ML pipelines for churn and revenue forecasting.',
},
{
id: 5,
name: 'Sam Rivera',
role: 'SRE',
department: 'Engineering',
bio: 'Manages uptime, incidents, and deployment pipelines.',
},
{
id: 6,
name: 'Taylor Brooks',
role: 'Sales Lead',
department: 'Sales',
bio: 'Responsible for enterprise accounts in EMEA.',
},
{
id: 7,
name: 'Casey Morgan',
role: 'HR Manager',
department: 'HR',
bio: 'Owns hiring, onboarding, and culture initiatives.',
},
{
id: 8,
name: 'Alex Kim',
role: 'Staff Engineer',
department: 'Engineering',
bio: 'Architect for the data platform and internal APIs.',
},
{
id: 9,
name: 'Morgan Lee',
role: 'Designer',
department: 'Design',
bio: 'Works on user research and prototype validation.',
},
{
id: 10,
name: 'Drew Park',
role: 'Analyst',
department: 'Analytics',
bio: 'Produces dashboards and weekly business metrics.',
},
{
id: 11,
name: 'Riley Nguyen',
role: 'Frontend Eng',
department: 'Engineering',
bio: 'Builds customer-facing product surfaces in React.',
},
{
id: 12,
name: 'Jamie Okafor',
role: 'Backend Eng',
department: 'Engineering',
bio: 'Maintains the microservices and event bus.',
},
];
const columns: TableColumn<Employee>[] = [
{ name: 'Name', selector: r => r.name, sortable: true },
{ name: 'Role', selector: r => r.role },
{ name: 'Department', selector: r => r.department, sortable: true },
];
type Mode = 'default' | 'custom-pagination' | 'custom-expander' | 'custom-sort';
const modes: { key: Mode; label: string }[] = [
{ key: 'default', label: 'Default icons' },
{ key: 'custom-pagination', label: 'Custom pagination' },
{ key: 'custom-expander', label: 'Custom expander' },
{ key: 'custom-sort', label: 'Custom sort' },
];
const ExpandedRow = ({ data: row }: { data: Employee }) => (
<div style={{ padding: '12px 24px', background: '#f9fafb', fontSize: 13, color: '#4b5563' }}>
<strong>{row.name}</strong> — {row.bio}
</div>
);
export default function IconsDemo() {
const [mode, setMode] = useState<Mode>('default');
const btnBase = 'px-2.5 py-1 rounded-md text-xs font-medium border transition-colors cursor-pointer';
const btnOn = 'bg-brand-600 text-white border-brand-600';
const btnOff = 'bg-white text-gray-600 border-gray-200 hover:border-gray-300';
const showExpander = mode === 'custom-expander' || mode === 'default';
return (
<div className="space-y-3">
<div className="flex flex-wrap items-center gap-2">
{modes.map(m => (
<button
key={m.key}
className={`${btnBase} ${mode === m.key ? btnOn : btnOff}`}
onClick={() => setMode(m.key)}
>
{m.label}
</button>
))}
</div>
<DataTable
columns={columns}
data={data}
pagination
paginationPerPage={5}
highlightOnHover
// Pagination icons
{...(mode === 'custom-pagination'
? {
paginationIcons: {
first: <ChevronsLeft />,
last: <ChevronsRight />,
previous: <ChevronLeft />,
next: <ChevronRight />,
},
}
: {})}
// Sort icon
{...(mode === 'custom-sort'
? {
sortIcon: <ArrowUp />,
}
: {})}
// Expandable rows
expandableRows={showExpander}
expandableRowsComponent={ExpandedRow}
{...(mode === 'custom-expander'
? {
expandableIcon: {
collapsed: <ChevronRightSmall />,
expanded: <ChevronDown />,
},
}
: {})}
/>
{mode === 'custom-pagination' && (
<p className="text-xs text-gray-400">Pagination icons replaced with inline SVG chevrons.</p>
)}
{mode === 'custom-expander' && (
<p className="text-xs text-gray-400">Expander icons replaced with inline SVG chevrons.</p>
)}
{mode === 'custom-sort' && (
<p className="text-xs text-gray-400">
Sort icon replaced with an up-arrow. Click a sortable column header to see it.
</p>
)}
</div>
);
}Override via props
Pass icons directly on a single table. Only the icons you supply are replaced. The rest fall back to the theme or built-in defaults.
import { ChevronUp, ChevronDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
// Sort icon — replaces the chevron shown in sortable column headers
<DataTable
sortIcon={<ChevronUp size={14} />}
columns={columns}
data={data}
/>
// Expander icons — collapsed / expanded state
<DataTable
expandableRows
expandableIcon={{
collapsed: <ChevronDown size={16} />,
expanded: <ChevronUp size={16} />,
}}
columns={columns}
data={data}
/>
// Pagination icons — any subset; omitted keys fall back to defaults
<DataTable
pagination
paginationIcons={{
previous: <ChevronLeft size={16} />,
next: <ChevronRight size={16} />,
first: <ChevronsLeft size={16} />,
last: <ChevronsRight size={16} />,
}}
columns={columns}
data={data}
/>Override via theme
Declare icons inside a theme so every table using it shares the same iconography without repeating props. Per-table props override the theme when explicitly passed.
import { createTheme } from 'react-data-table-component';
import { ChevronUp, ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';
createTheme('icon-theme', {
icons: {
sort: <ChevronUp size={14} />,
expandable: {
collapsed: <ChevronDown size={16} />,
// expanded: omitted — falls back to the built-in default
},
pagination: {
next: <ChevronRight size={16} />,
previous: <ChevronLeft size={16} />,
// first/last: omitted — fall back to defaults
},
},
});
<DataTable columns={columns} data={data} theme="icon-theme" />;Custom checkbox component
Replace the built-in checkbox with your own component via selectableRowsComponent. Extra props can be forwarded through selectableRowsComponentProps. The component receives standard HTMLInputElement props and a ref. DataTable sets el.indeterminate directly on the ref to handle the partial-selection state.
import Checkbox from '@mui/material/Checkbox';
<DataTable selectableRows selectableRowsComponent={Checkbox} selectableRowsComponentProps={{ color: 'primary' }} />;If you build your own checkbox, use React.forwardRef and forward the ref to the underlying <input> element so DataTable can set indeterminatenatively:
import React from 'react';
const MyCheckbox = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>((props, ref) => (
<input {...props} ref={ref} style={{ accentColor: 'teal', width: 18, height: 18 }} />
));
MyCheckbox.displayName = 'MyCheckbox';
<DataTable selectableRows selectableRowsComponent={MyCheckbox} />;Header & column separators in themes
The headerSeparator and columnSeparator props can be baked into a theme so every table using it shares the same separator style. The per-table prop always takes precedence when explicitly passed.
createTheme(
'grid',
{
primary: '#1976d2',
headerSeparator: 'full', // bold line below header row
columnSeparator: 'full', // full-height lines between columns
},
'default',
);
// Override for one specific table
<DataTable theme="grid" columnSeparator={false} />;Column separators
Toggle between no separators, subtle inset lines, and full-height lines.
// No body separators (default)
<DataTable columns={columns} data={data} />
// Subtle body separators (60%-height inset)
<DataTable columns={columns} data={data} columnSeparator />
// Full-height body separators
<DataTable columns={columns} data={data} columnSeparator="full" />
// Remove the default header separator
<DataTable columns={columns} data={data} headerSeparator={false} />CSS variable override
Every theme compiles to CSS custom properties scoped to DataTable's wrapper element. Override any variable with a wrapper class. No createTheme() needed for small tweaks.
.my-table {
--rdt-color-text-primary: #1a1a2e;
--rdt-color-bg: #f0f4ff;
--rdt-color-header-bg: #e8edff; /* separate header background */
--rdt-color-divider: #c7d2fe;
--rdt-color-highlight: #e0e7ff;
--rdt-color-selected: #ede9fe;
--rdt-border-radius: 8px;
--rdt-font-size: 13px;
}CSS variable reference
| Variable | Default | What it controls |
|---|---|---|
--rdt-color-scheme | light | CSS color-scheme: affects native checkbox, select, and scrollbar rendering |
--rdt-color-primary | #1976d2 | Checkbox accent, active sort icon, selection ring |
--rdt-color-text-primary | rgba(0,0,0,.87) | Primary text in rows and header cells |
--rdt-color-text-secondary | rgba(0,0,0,.54) | Muted text: group labels, pagination count |
--rdt-color-text-disabled | rgba(0,0,0,.38) | Disabled row text |
--rdt-color-bg | #fff | Table and row background |
--rdt-color-header-bg | falls back to --rdt-color-bg | Header row background: set only when the theme or CSS provides it |
--rdt-color-divider | rgba(0,0,0,.12) | Row borders and separator lines |
--rdt-color-context-bg | #e3f2fd | Selected-rows context bar background |
--rdt-color-context-text | rgba(0,0,0,.87) | Selected-rows context bar text |
--rdt-color-selected | #e3f2fd | Selected row background |
--rdt-color-selected-text | rgba(0,0,0,.87) | Selected row text |
--rdt-color-highlight | #eee | Hover highlight background |
--rdt-color-highlight-text | rgba(0,0,0,.87) | Hover highlight text |
--rdt-color-striped | #fafafa | Striped (even) row background |
--rdt-color-striped-text | rgba(0,0,0,.87) | Striped row text |
--rdt-color-btn | rgba(0,0,0,.54) | Sort icon, expander chevron, pagination button icons |
--rdt-color-btn-hover | rgba(0,0,0,.08) | Pagination button hover background |
--rdt-color-btn-focus | rgba(0,0,0,.12) | Pagination button focus ring |
--rdt-color-btn-disabled | rgba(0,0,0,.18) | Disabled pagination button icon |
--rdt-row-height | 52px | Body row min-height |
--rdt-header-height | 56px | Header row min-height |
--rdt-cell-padding-x | 16px | Horizontal padding inside every cell |
--rdt-font-size | 13px | Body cell font size |
--rdt-font-size-header | 12px | Header cell font size |
--rdt-font-family | inherits | Font family for all table text |
--rdt-border-radius | 4px | Pill radius on selection badges and other rounded elements |
Prop reference
| Prop | Type | Default | Description |
|---|---|---|---|
theme | ThemeProp | "default" | Named theme (e.g. "material", "catppuccin", "crisp"). |
colorMode | ColorMode | "system" | Controls when the theme's dark-mode overrides apply. "system" follows localStorage, the html.dark class, then prefers-color-scheme. "light" / "dark" force a mode. |
selectableRowsComponent | "input" | ReactNode | built-in checkbox | Custom checkbox component. |
selectableRowsComponentProps | object | - | Extra props forwarded to the custom checkbox component. Function values are called with the checkbox's indeterminate state and their return value is passed instead. |
headerSeparator | boolean | "subtle" | "full" | true | Vertical lines between header cells. true/"subtle" = inset line (default), "full" = full-height, false = none. |
columnSeparator | boolean | "subtle" | "full" | - | Body column separators (headers always shown). |
customStyles | TableStyles | - | Fine-grained style overrides. See Custom styles. |