Collaboration

Why your green rule never fires

Two conditional style rules, one row, and the order you put them in decides the colour — plus the four places the styling engine quietly disagrees with the filters on your API.

SchemaStack team23 Aug 2026Verified working · 23 Aug 2026Docs

You add two conditional style rules. amount > 100 paints the row red. amount > 1000 paints it green. Then every row over a thousand comes out red, and the green rule appears to do nothing at all.

Nothing is broken. Rules are checked in priority order and evaluation stops at the first match. A row of 5000 is over 100, so the red rule matches, and the green rule below it is never reached.

amount = 5000As writtenpriority 1 · amount > 100 → redmatches · evaluation stops herepriority 2 · amount > 1000 → greenwould match, but is never evaluatedthe row is painted redMost specific firstpriority 1 · amount > 1000 → greenmatches · evaluation stops herepriority 2 · amount > 100 → redstill reachable — for rows of 101 to 1000the row is painted greenOrder narrowest to broadest. A rule that is a superset of the one below it makes that rule dead.Every box above is labelled as well as coloured — colour alone is the one channel some of your readers do not have.
The same two rules, in two orders, against the same row. Evaluation stops at the first match by ascending priority — so the broader rule placed first hides the narrower one completely, and the order decides the colour.

The order is the logic

Rules are evaluated in ascending priority — the order you drag them into. The first enabled rule whose conditions match decides the row's style, and the rest are not evaluated for that row.

That gives you a short checklist when a colour surprises you:

  • Narrowest first. If one rule's conditions are a superset of another's and sits above it, the lower rule is dead code. > 1000 belongs above > 100, not below it.
  • Disabled rules are skipped entirely, so you can park a rule without deleting it and the next one down takes over.
  • A rule with no conditions never matches. An empty rule is inert rather than universal, which is the safer of the two options when you are halfway through writing one.

The conditions are the ones you already know

Each rule holds one or more conditions joined by AND or OR, drawn from the same operator set as filtering: equals, not equals, the four comparisons, LIKE, IN, NOT IN, starts with, ends with. The OR is worth noting on its own — the REST API's filter syntax has no way to express it, so a rule like overdue or unassigned is something you can say here and not there.

Conditions can target any column in the view, and two column types behave better than you might expect:

Formula columns. A formula doesn't always declare a numeric type, but it often produces numbers. The evaluator checks the values at runtime as well as the declared type, so a computed line_total compares as a number rather than as text — 9 sorts below 10 instead of after it.

Relationship columns. The condition is compared against the label the cell displays, not the underlying foreign key. You write customer = Acme Ltd, which is the thing you can actually read on screen, rather than looking up that the row points at id 42. For a many-to-many cell holding several labels, LIKE %globex% matches within it.

It runs in your browser

Styles are resolved client-side against the rows already loaded — 100 per page. Nothing is added to your database, no query gets slower, and there is no index to create. A row that arrives over SSE because a colleague just created it picks up its colour as it lands.

Rules belong to the view, not to you and not to a saved preset. So everyone who opens that view sees the same rows lit up the same way, without anyone having to load anything — which is the point when the colour is what the team is coordinating on. It also means a rule you add is a rule your colleagues get, so the palette is a shared decision rather than a personal one.

The flip side of client-side evaluation is worth stating plainly. Styling is not searching. It colours what is on screen. If the row you care about is on page seven, it gets its colour when you reach page seven — so for finding things, filter; for recognising things once they are in front of you, style.

It is not quite the same engine as your API filters

The operator names match, but the styling engine is a separate implementation running in the browser, and it disagrees with the server in four places. All four are the kind of thing you notice once and then never forget:

Filter on the APIStyle rule in the grid
LIKE acmematches Acme Ltd — a bare value is wrapped as %acme%matches only acme — the pattern is anchored at both ends
Casecase-sensitive; Postgres gets a plain LIKEcase-insensitive, and Unicode-normalised first
active = taccepted — t, f, y, n, 0 and 1 all parsesilently false; only true, 1 and yes read as true
An unparseable valuerejected with an error you can seeno error — the condition simply never matches

None of these is wrong on its own. Together they mean a style rule and the equivalent API filter can legitimately disagree about the same row, so it is worth writing %acme% explicitly when you mean a substring — that behaves identically on both sides.

What it doesn't do (yet)

  • Rows, not cells. The resolved style is applied to the whole row. You choose which column each condition tests, but you cannot paint one cell differently from its neighbours.
  • Background colour, text colour, bold, italic and strikethrough. That is the full set. There is no font size, no borders, no icons, no in-cell data bars.
  • Eight of each colour. Both palettes are theme-aware, with a light and a dark value per entry, and every text colour clears WCAG AA against white, against its own pastel background, and against the dark surface. Colours are validated as six-digit hex on the way in — an arbitrary string used to slip through the API and render inline, invisible in dark mode.
  • Page-scoped, as above.

One last thing, given what this feature is for. If people are relying on a glance to tell them something, colour is the one channel a meaningful share of them do not have — roughly one man in twelve cannot separate your red rule from your green one. That is exactly why bold, italic and strikethrough are in the list rather than decoration around the edges of it. Pair the colour with one of them and the row still says what it means to everybody looking at it.

Verified 23 Aug 2026: 54 unit tests written for this post against the two utilities that decide a row's colour — priority ordering and first-match-wins, disabled and empty rules, AND/OR logic, all eleven operators, null and zero handling, boolean and numeric coercion, LIKE pattern translation, and the two preset-colour class maps, including that a background hex never resolves to a text class or the reverse. The eight text colours were checked for WCAG AA contrast against white, against their own pastel background and against the dark surface; the worst pair is orange at 5.11:1. The four server-side behaviours in the comparison table were read from FilterValueCoercion and WorkspaceDatabaseService, which emit a plain case-sensitive LIKE and accept t/f/y/n for booleans..