Home/Cells & Formatting/Conditional Formatting
Cells & Formatting

Conditional Formatting

Colour cells and rows from their values — numeric thresholds, ranges, text matches, status icons and row tints — without writing a single cell template.

column.conditionalFormats

Declarative rules per column. Day % crosses two thresholds, P/E highlights a band, Shares shows what happens when two rules match one cell, and Signal uses a predicate no operator could express.

Ticker
Sector
Day %
P/E
Shares
Signal
AURA
Tech
3.4
28.1
1200
Buy
NMBS
Industrial
-1.9
14.6
300
Hold
PULS
Tech
6.1
41.7
150
Buy
DRFT
Consumer
-4.7
9.2
2400
Sell
VRTX
Health
0.3
18
640
Hold
CBLT
Energy
-8.2
6.8
900
Sell
HALO
Tech
1.2
33.5
210
Buy
LOFT
Industrial
-0.4
16.2
500
Hold
ECHO
Consumer
2.8
12
5000
Buy
SLAT
Health
-2.1
25
120
Hold
BCON
Energy
5.6
11.1
340
Buy
ATLS
Tech
2.5
52.3
80
Sell
All records loaded
12 / 12 records

One rule per condition, in the order you write them

Each rule tests the cell's value. A match applies an inline style, a className, an icon before the value — or all three. Day % above has two rules that cannot both match, which is the simple case: below zero is red with a ▼, at or above +2% is green with a ▲, and everything between is left alone.

conditionalFormats: [
  { op: 'lt',  value: 0, style: { color: '#dc2626' }, icon: '▼' },
  { op: 'gte', value: 2, style: { color: '#16a34a' }, icon: '▲' },
]

When two rules match one cell

Rules run top to bottom and the matches merge — they do not compete. The Shares column has three rules over one number, and the two big positions show what that means:

  • 5000 matches the first two rules. The colour is the second rule's — a later rule wins a style key it also sets. So does the icon: the last matched icon is the one you see, 🔥 rather than ⚠.
  • That second rule carries stopIfMatched: true, so the third rule never runs and the cell is not underlined.
  • 2400 matches only the first rule, then the third: amber, ⚠, and underlined. Same three rules, a different outcome, decided entirely by the value.

classNames behave differently from styles: every matched rule's class is added, so nothing overrides anything.

The operators, and the escape hatch

eq · neq · gt · gte · lt · lte · between · in · contains · startsWith · endsWith · empty · notEmpty.

  • between uses value and value2 and is inclusive at both ends — the P/E band above is 12 to 25, and a P/E of exactly 12 is inside it.
  • in takes an array: the Sector column italicises Energy and Consumer with a single rule.

When no operator fits, pass when instead. It receives the value and the whole row, so it can compare two fields — which is the one thing the operators cannot do. The Signal column marks a holding that is up on the day while the analyst says Sell, and only one row on the page qualifies:

{ when: (value, row) => row.changePct > 0 && value === 'Sell',
  style: { color: '#dc2626', fontWeight: '700' }, icon: '↯' }

when takes precedence over op: if you set both, the operator is ignored.

column.cellStyle

One style for every cell in a column, or a function per row. Use it for alignment and number formatting, and for rules the operators cannot express.

Ticker
Price
Shares
Analyst
AURA
184.2
1200
Buy
NMBS
62.05
300
Hold
PULS
412.88
150
Buy
DRFT
19.42
2400
Sell
VRTX
97.6
640
Hold
CBLT
44.18
900
Sell
HALO
233.04
210
Buy
LOFT
78.91
500
Hold
ECHO
11.07
5000
Buy
SLAT
156.33
120
Hold
BCON
88.45
340
Buy
ATLS
305.77
80
Sell
All records loaded
12 / 12 records

Two forms, one property

cellStyle is for styling that is not a condition. Give it a flat map to style every cell in the column — Price above is right-aligned with tabular numerals, so the digits line up as the rows scroll. Give it a function (row, value) => CellStyle for per-row logic, and return an empty object to mean "leave this cell alone".

// static
cellStyle: { fontVariantNumeric: 'tabular-nums', textAlign: 'right' },

// per row
cellStyle: (row, value) => value >= 1000 ? { fontWeight: '700' } : {},

When to reach for this instead of a rule

A rule is a better home for anything a reader would describe as a threshold, because the intent is readable at a glance and it survives being reordered. cellStyle earns its place for column-wide presentation, and for logic with no natural operator.

cellClass is the same property with a class instead of a style, in both forms — use it when the styling belongs in your stylesheet. The two combine: a class from cellClass and a class from a matched rule both end up on the cell. The Analyst column above marks Sell with a class, and the colour comes from CSS.

Two things to get right when you do that. The cell is rendered by the grid, so a component-scoped selector will not reach it — reach through with ::ng-deep (or put the rule in a global stylesheet). And make the selector specific enough to win: a bare .is-sell ties with the grid's own .gc-cell rule and loses, because the library's stylesheet is loaded later. Include the cell:

:host ::ng-deep .gc-cell.is-sell {
  color: #dc2626;
  font-weight: 700;
}

Do not put layout properties in here

width, flex, min-width and max-width belong to the column, not to a style map. A cellStyle lands on the very same element the grid sizes, so a width set here overrides the column's own width and breaks the layout — it is not filtered out for you. Set sizing through width / sizing / minWidth on the column instead.

config.rowStyle

Styles the whole row. Here every Sell-rated holding is tinted red and every other row is striped — one function, both jobs.

Ticker
Sector
Analyst
Day %
AURA
Tech
Buy
3.4
NMBS
Industrial
Hold
-1.9
PULS
Tech
Buy
6.1
DRFT
Consumer
Sell
-4.7
VRTX
Health
Hold
0.3
CBLT
Energy
Sell
-8.2
HALO
Tech
Buy
1.2
LOFT
Industrial
Hold
-0.4
ECHO
Consumer
Buy
2.8
SLAT
Health
Hold
-2.1
BCON
Energy
Buy
5.6
ATLS
Tech
Sell
2.5
All records loaded
12 / 12 records

One function, the whole row

config.rowStyle takes a flat map, or a function of (row, index). The index is what makes striping possible, and the row is what makes a status tint possible — the grid above does both in one expression, and the status tint wins where the two overlap:

rowStyle: (row, index) =>
  row.rating === 'Sell' ? { background: 'rgba(220,38,38,.08)' }
  : index % 2 === 1     ? { background: 'rgba(100,116,139,.06)' }
  : {},

Sort by Analyst and watch what happens: the red rows gather together and the stripes re-lay themselves around them, because index is the row's position now, not its position in your data.

Row and cell styling stack — a row background shows through wherever a cell sets none of its own.

Value-driven cell & row styling — no templates required

Conditional formatting paints cells and rows straight from their data, so you do not have to write a cellTemplate just to change a colour. Everything on this page is in the free package. There are three entry points, and they are the three examples below:

  • column.conditionalFormats — a list of declarative rules. Reach for this first.
  • column.cellStyle — one style for the whole column, or a function per row.
  • config.rowStyle — the whole row.

They compose. A cell can take a column-wide cellStyle, have a matched rule override part of it, and sit on a row tinted by rowStyle — all at once. Where a cell rule and a cellStyle set the same property, the rule wins.

Formatting API

PropertyTypeDefaultDescription
column.conditionalFormatsConditionalFormatRule[]—Declarative rules, evaluated top → bottom. Matches merge: a later rule wins a style key it also sets, classNames accumulate, and the last matched icon is shown.
rule.op'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'between' | 'in' | 'contains' | 'startsWith' | 'endsWith' | 'empty' | 'notEmpty'—The value test. between uses value + value2 and is inclusive at both ends; in takes an array.
rule.when(value, row) => boolean—Predicate form — sees the whole row, so it can compare fields. Takes precedence over op.
rule.styleCellStyle—Inline styles applied when the rule matches. Merged with earlier matches, key by key.
rule.classNamestring—A class added when the rule matches. Every matched rule contributes; none overrides another.
rule.iconstring—A glyph shown before the value. When several matched rules set one, the last wins.
rule.stopIfMatchedbooleanfalseHalt evaluation after this rule matches, so no later rule can add to the cell.
column.cellStyleCellStyle | (row, value) => CellStyle—Static or per-row styling for every cell in the column. A matched rule overrides it key by key.
column.cellClassstring | (row, value) => string | Record<string, boolean>—The same two forms with a class instead of a style. Combines with rule classNames.
config.rowStyleCellStyle | (row, index) => CellStyle—Styles the whole row. index is the row's current display position, so striping follows the sort.