Theming and labels

Built-in palettes, surface tones, class-driven dark mode, and replacing every built-in string for i18n.

Theming

// built-in palette per list
defineListConfig({ colorTheme: 'teal', /* … */ })

// global default
<ListKitProvider theme="teal">…</ListKitProvider>

// custom theme (brand colors) — pass a ThemeClasses object anywhere a theme is accepted
const brand: ThemeClasses = {
  primaryBg: 'bg-[#121c38]',
  primaryText: 'text-white',
  focusRing: 'focus:ring-indigo-500',
  focusBorder: 'focus:border-indigo-500',
  /* … */
}
defineListConfig({ colorTheme: brand, /* … */ })

Table & card tones

colorTheme drives the accents; tones drives the neutral chrome — the table's header, dividers, row hover/selected states, and the panel around the table and the default cards. Pick a built-in preset ('gray' is the default, 'contrast' inverts the header) or pass a full SurfaceTones object of Tailwind classes. The two axes compose: colorTheme: 'teal', tones: 'slate'.

// preset: 'gray' | 'slate' | 'zinc' | 'contrast'
defineListConfig({ tones: 'slate' /* … */ })

// custom — keep every background opaque: pinned cells inherit the row's
defineListConfig({
	tones: {
		container: 'border-indigo-100 bg-white shadow-sm',
		headerBg: 'bg-indigo-50',
		headerText: 'text-indigo-700',
		headerDivider: 'border-indigo-200',
		rowBg: 'bg-white',
		rowHover: 'hover:bg-indigo-50',
		rowSelected: 'bg-indigo-100 hover:bg-indigo-100',
		divider: 'divide-indigo-100',
	},
	/* … */
})

The Table primitive takes the same value as a tones prop for standalone use, and getSurfaceTones resolves a preset name if you need the classes yourself.

Dark mode

Every component ships additive dark: variants keyed on a .dark class on an ancestor (usually <html>) — not on prefers-color-scheme — so your app owns the toggle. listkit/tailwind.css registers the variant for you:

@custom-variant dark (&:where(.dark, .dark *));

Importing that file is enough. Without it Tailwind v4 reads dark: as prefers-color-scheme, and a light-only app renders its lists dark for every reader whose OS is — the rest of the page unchanged.

Toggle document.documentElement.classList.toggle('dark') and every list — table, cards, menus, filters, dialogs, skeletons — follows. The built-in palettes carry dark accent variants; a custom ThemeClasses can append its own dark: classes inside each field. Light rendering is untouched when the class is absent.

Labels (i18n)

Controls describable by an icon (view toggle, filter button, results count) are icon-only, so they read the same in any language; their names live in aria-label/title. Every other built-in string comes from a labels object that defaults to English — override it app-wide on the provider, or per list via config.labels.

Quickest path — DEFAULT_LABELS (English) and ES_LABELS (Spanish) cover the common cases in one line (override individual keys on top if needed):

import { ListKitProvider, ES_LABELS } from 'listkit'
// whole app in Spanish (English is the default, so no prop needed for English)
;<ListKitProvider labels={ES_LABELS}></ListKitProvider>

Or hand-pick the strings:

// app-wide (the app's language)
;<ListKitProvider
	labels={{
		tableView: 'Vista tabla',
		cardsView: 'Vista tarjetas',
		filters: 'Filtros',
		applyFilters: 'Aplicar',
		clearFilters: 'Limpiar',
		empty: 'Sin resultados',
		yes: 'Sí',
		no: 'No',
		results: n => `${n} resultado${n === 1 ? '' : 's'}`,
	}}
>

</ListKitProvider>

// or per list (wins over the provider)
defineListConfig({ labels: { empty: 'Sin pedidos' } /* … */ })

NextListView accepts the same labels (and theme) prop and forwards it to its internal provider: <NextListView labels={ES_LABELS} config={…} adapter={…} />.

Resolution order: config.labels → provider labelsDEFAULT_LABELS. The existing per-item props still win where they exist (config.emptyMessage, config.filtersTitle, a filter's trueLabel/falseLabel). See ListLabels for the full key list.

On this page