Dark Mode
Inertia Table uses Tailwind's dark: variant for all styling. Every default class has a light and dark counterpart - backgrounds, text colours, borders, hover states, badge variants, and sort indicators all adapt automatically. No configuration is required beyond having dark mode working in your Tailwind setup.
How It Works
The package uses the dark: class variant throughout. When the dark class is present on your <html> element (or whichever element your Tailwind config targets), the dark styles activate.
Default Light vs Dark
| Element | Light | Dark |
|---|---|---|
| Wrapper | bg-white border-gray-200 | bg-gray-900 border-gray-700 |
| Header | bg-gray-50 text-gray-600 | bg-gray-800 text-gray-400 |
| Rows | text-gray-900 hover:bg-gray-50 | text-gray-200 hover:bg-gray-800 |
| Borders | border-gray-200 divide-gray-100 | border-gray-700 divide-gray-700 |
| Search | bg-white border-gray-300 | bg-gray-800 border-gray-600 |
| Pagination | border-gray-300 text-gray-500 | border-gray-600 text-gray-400 |
| Badges | Light tinted backgrounds | Dark tinted backgrounds |
Tailwind Configuration
Tailwind v4
Tailwind v4 uses a CSS-based dark mode by default. If you're using the class strategy, add the custom variant to your CSS:
/* resources/css/app.css */
@custom-variant dark (&:where(.dark, .dark *));Tailwind v3
In tailwind.config.js, set the dark mode strategy:
module.exports = {
darkMode: 'class',
// ...
}The class strategy is recommended because it gives you programmatic control over the theme. The media strategy (which follows the OS preference) also works but can't be toggled by users.
Toggling Dark Mode
The table itself doesn't manage dark mode state - it reacts to the dark class on your document. Here's a common pattern for toggling between system, light, and dark modes:
import { useState, useEffect } from 'react';
type ThemeMode = 'system' | 'light' | 'dark';
function applyTheme(mode: ThemeMode) {
const root = document.documentElement;
if (mode === 'dark') {
root.classList.add('dark');
} else if (mode === 'light') {
root.classList.remove('dark');
} else {
// System preference
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
root.classList.add('dark');
} else {
root.classList.remove('dark');
}
}
}
function useTheme() {
const [mode, setMode] = useState<ThemeMode>(() => {
if (typeof window === 'undefined') return 'system';
return (localStorage.getItem('theme') as ThemeMode) || 'system';
});
useEffect(() => {
applyTheme(mode);
localStorage.setItem('theme', mode);
if (mode === 'system') {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
const handler = () => applyTheme('system');
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}
}, [mode]);
return { mode, setMode };
}<script setup lang="ts">
import { ref, watch, onMounted } from 'vue';
type ThemeMode = 'system' | 'light' | 'dark';
function applyTheme(mode: ThemeMode) {
const root = document.documentElement;
if (mode === 'dark') {
root.classList.add('dark');
} else if (mode === 'light') {
root.classList.remove('dark');
} else {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
root.classList.add('dark');
} else {
root.classList.remove('dark');
}
}
}
const mode = ref<ThemeMode>(
(localStorage.getItem('theme') as ThemeMode) || 'system'
);
watch(mode, (newMode) => {
applyTheme(newMode);
localStorage.setItem('theme', newMode);
});
onMounted(() => {
applyTheme(mode.value);
if (mode.value === 'system') {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
mq.addEventListener('change', () => applyTheme('system'));
}
});
</script>Overriding Dark Styles
Since all styling is done via Tailwind classes, you can override any dark mode colour using the classNames prop. Overrides replace the entire class string for that element, so include both light and dark variants.
Full Theme Override
<InertiaTable
tableData={servers}
classNames={{
wrapper: 'rounded-xl border border-slate-200 bg-white shadow dark:border-slate-700 dark:bg-slate-900',
thead: 'bg-slate-50 border-b border-slate-200 dark:bg-slate-800 dark:border-slate-700',
th: 'px-4 py-3 text-left text-xs font-semibold text-slate-600 uppercase dark:text-slate-400',
tbody: 'divide-y divide-slate-100 dark:divide-slate-700',
tr: 'hover:bg-slate-50 dark:hover:bg-slate-800',
td: 'px-4 py-3 text-sm text-slate-900 dark:text-slate-200',
}}
/><InertiaTable
:table-data="servers"
:class-names="{
wrapper: 'rounded-xl border border-slate-200 bg-white shadow dark:border-slate-700 dark:bg-slate-900',
thead: 'bg-slate-50 border-b border-slate-200 dark:bg-slate-800 dark:border-slate-700',
th: 'px-4 py-3 text-left text-xs font-semibold text-slate-600 uppercase dark:text-slate-400',
tbody: 'divide-y divide-slate-100 dark:divide-slate-700',
tr: 'hover:bg-slate-50 dark:hover:bg-slate-800',
td: 'px-4 py-3 text-sm text-slate-900 dark:text-slate-200',
}"
/>Dark-Only Adjustments
If you only need to tweak the dark variant while keeping the light defaults, override the specific key and rewrite both sides:
<InertiaTable
tableData={servers}
classNames={{
// Keep the light default, change dark background to zinc
wrapper: 'rounded-lg border border-gray-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900',
tr: 'hover:bg-gray-50 transition-colors dark:hover:bg-zinc-800',
}}
/><InertiaTable
:table-data="servers"
:class-names="{
wrapper: 'rounded-lg border border-gray-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900',
tr: 'hover:bg-gray-50 transition-colors dark:hover:bg-zinc-800',
}"
/>Branded Dark Theme
// A purple-tinted dark theme
const brandedClassNames = {
wrapper: 'rounded-lg border border-gray-200 bg-white shadow-sm dark:border-purple-900 dark:bg-gray-950',
thead: 'bg-gray-50 border-b border-gray-200 dark:bg-purple-950/50 dark:border-purple-900',
th: 'px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase dark:text-purple-300',
tr: 'hover:bg-gray-50 transition-colors dark:hover:bg-purple-950/30',
td: 'px-4 py-3 text-sm text-gray-900 dark:text-purple-100',
search: 'rounded-md border border-gray-300 bg-white px-3 py-2 text-sm focus:border-purple-500 focus:ring-purple-500 dark:border-purple-800 dark:bg-gray-900 dark:text-purple-100',
};
<InertiaTable tableData={servers} classNames={brandedClassNames} /><script setup lang="ts">
// A purple-tinted dark theme
const brandedClassNames = {
wrapper: 'rounded-lg border border-gray-200 bg-white shadow-sm dark:border-purple-900 dark:bg-gray-950',
thead: 'bg-gray-50 border-b border-gray-200 dark:bg-purple-950/50 dark:border-purple-900',
th: 'px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase dark:text-purple-300',
tr: 'hover:bg-gray-50 transition-colors dark:hover:bg-purple-950/30',
td: 'px-4 py-3 text-sm text-gray-900 dark:text-purple-100',
search: 'rounded-md border border-gray-300 bg-white px-3 py-2 text-sm focus:border-purple-500 focus:ring-purple-500 dark:border-purple-800 dark:bg-gray-900 dark:text-purple-100',
};
</script>
<template>
<InertiaTable :table-data="servers" :class-names="brandedClassNames" />
</template>Row-Level Dark Styling
Use rowClassName for conditional dark mode styling per row:
<InertiaTable
tableData={servers}
rowClassName={(row) => {
if (row.status === 'failed') return 'bg-red-50 dark:bg-red-950/30';
if (row.status === 'pending') return 'bg-yellow-50 dark:bg-yellow-950/20';
return '';
}}
/><InertiaTable
:table-data="servers"
:row-class-name="(row) => {
if (row.status === 'failed') return 'bg-red-50 dark:bg-red-950/30';
if (row.status === 'pending') return 'bg-yellow-50 dark:bg-yellow-950/20';
return '';
}"
/>Badge Colours in Dark Mode
Badge variants automatically switch between light and dark palettes:
| Variant | Light | Dark |
|---|---|---|
success | bg-green-100 text-green-800 | bg-green-900 text-green-200 |
danger | bg-red-100 text-red-800 | bg-red-900 text-red-200 |
warning | bg-yellow-100 text-yellow-800 | bg-yellow-900 text-yellow-200 |
info | bg-blue-100 text-blue-800 | bg-blue-900 text-blue-200 |
default | bg-gray-100 text-gray-800 | bg-gray-700 text-gray-200 |
gray | bg-gray-100 text-gray-700 | bg-gray-700 text-gray-300 |
destructive | bg-red-100 text-red-800 | bg-red-900 text-red-200 |
outline | border-gray-300 text-gray-700 | border-gray-600 text-gray-300 |
These are built into the package. To customise badge colours, use a cellRenderers override (React) or a #cell-{columnName} slot (Vue) on the specific column - see Overriding Cells.
CSS Variables (shadcn)
If you're using shadcn/ui with CSS variable-based theming, the table's default Tailwind classes won't automatically match your theme tokens. Use the classNames prop to map to your CSS variables, or use the useTable hook with shadcn components directly - see shadcn/ui Integration.