Conditional Styling

Style rows, cells and headers from their data, and format numbers with colour scales, data bars and icon sets.

styleRules applies CSS classes conditionally, based on row data or column identity — no per-render inline style computation, and no separate compiler step. Pass an array of rule objects to <Grid styleRules={...} />; the grid compiles and re-applies them whenever the array reference changes.

import { useMemo } from 'react';
import { Grid, type ColumnDef, type StyleRule } from '@witbybit/wit-grid-react';

interface ProductRow {
	id: string;
	name: string;
	price: number;
	status: 'Active' | 'Inactive';
}

export function ProductGrid({ rows, columns }: { rows: ProductRow[]; columns: ColumnDef<ProductRow>[] }) {
	const styleRules = useMemo<StyleRule<ProductRow>[]>(
		() => [
			{
				kind: 'row',
				when: (row) => row.status === 'Inactive',
				rowClass: 'row-inactive',
			},
			{
				kind: 'cell',
				field: 'price',
				when: (row) => row.price > 500,
				cellClass: 'cell-high-value',
			},
			{
				kind: 'headerCell',
				field: 'price',
				when: () => true,
				headerCellClass: 'header-emphasis',
			},
		],
		[]
	);

	return <Grid rows={rows} columns={columns} getRowId={(row) => row.id} styleRules={styleRules} />;
}

Rule kinds

kindTargetsClass fieldwhen receives
rowA data rowrowClass(row, params)
groupRowA group rowrowClass(visualRow) — optional, defaults true
detailRowA master-detail rowrowClass(visualRow) — optional, defaults true
cellA cell, optionally scoped to fieldcellClass(row, col, params)
headerCellA header cell, optionally scoped to fieldheaderCellClass(col)

field on cell and headerCell rules is optional — omit it to evaluate the rule against every column. Rules are additive: a row or cell can match several rules at once, and all matching classes are applied together.

Prefer styleRules for conditional decoration

Use styleRules for data-driven decoration (status colors, thresholds, validation highlights). Use the theming API for token-level, grid-wide visual changes (surface colors, borders, fonts).

Conditional formatting

Three more rule kinds format a cell by where its number sits in its column: a colour scale tints it, a data bar draws a bar behind it, an icon set puts an arrow, dot or signal before it.

import type { StyleRule } from '@witbybit/wit-grid-react';

type Deal = { id: string; amount: number; margin: number; growth: number; latency: number };

const styleRules: StyleRule<Deal>[] = [
	{ kind: 'dataBar', field: 'amount' },
	{ kind: 'colorScale', field: 'margin' }, // red → amber → green
	{ kind: 'iconSet', field: 'growth', icons: 'arrows' },
	{ kind: 'iconSet', field: 'latency', icons: 'dots', reverse: true }, // low is good
];
kindOptions
colorScalecolors (two or three CSS colours, low → high), mid, opacity (default 0.24)
dataBarcolor (default the theme accent), negativeColor (default red)
iconSeticons: 'arrows' · 'dots' · 'signal'; thresholds (default [1/3, 2/3]); reverse

Every kind takes field and optional fixed min / max.

  • The range is the column's, over the rows currently displayed: filter the grid and the scale re-fits. Give min / max to keep cells comparable across filters.
  • Live data moves it. When a visible cell shows a new value the range is recomputed (at most every 200 ms) and the visible cells repaint. Infinite and server row models keep a running range of the values seen.
  • Data bars grow from zero, so gains and losses read both ways.
  • Rules stack on one field: the icon draws over the bar, the bar over the tint.
  • Colours are mixed by the browser, so palette names, any CSS colour and theme variables all work.
  • Cheap: painted as background layers on the cell itself (no extra elements), written only when a cell's look changes.
  • Excel keeps them: exportExcel writes them as native conditional formats.

Row animations

When a sort, a live value change under the current sort, a group expanding or a detail row opening moves rows, they animate to their new place. Scrolling never animates, and users who prefer reduced motion get instant changes. Animations use only transform and opacity, so they run on the GPU and cost nothing per frame.

rowAnimation chooses how they move. Change it any time; it applies from the next change.

<Grid rows={rows} columns={columns} getRowId={(row) => row.id} rowAnimation={{ style: 'slide', easing: 'spring', duration: 520, stagger: 18 }} />
OptionDefaultEffect
style'slide''slide': rows glide from their old place. 'fade': moved rows fade in at their new place. 'none'.
easing'smooth''smooth', 'snappy' (quick start, soft stop), 'spring' (settles with a small overshoot), or any CSS easing.
duration280Milliseconds per row.
stagger0Milliseconds between rows, top to bottom, so they cascade into place. The whole cascade is capped at 240 ms.
onall{ sort, liveReorder, expand, detail }: turn individual kinds of change off.

Outside React, pass rowAnimation when creating the grid and change it with api.setOptions({ rowAnimation }). The Realtime Dashboard has a Motion picker to compare styles on a live-sorted feed.

Edit on GitHub

Last updated on

On this page