Conditional Formatting Table — Live Threshold Cell Coloring HTML CSS JS
Conditional Formatting Table · Tables · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="tcf-card">
<div class="tcf-head">
<h3>Rep performance — units sold</h3>
</div>
<div class="tcf-controls">
<label>Low threshold<input type="number" id="tcfLow" value="40" min="0" max="200"></label>
<label>High threshold<input type="number" id="tcfHigh" value="80" min="0" max="200"></label>
</div>
<table class="tcf-table" id="tcfTable">
<thead><tr><th>Rep</th><th>Jan</th><th>Feb</th><th>Mar</th><th>Apr</th><th>May</th><th>Jun</th></tr></thead>
<tbody id="tcfBody"></tbody>
</table>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#faf7ff;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.tcf-card{background:#fff;border-radius:14px;padding:16px;width:100%;max-width:620px;box-shadow:0 18px 44px rgba(88,28,135,.1);border:1px solid #ede4fb}
.tcf-head{margin-bottom:10px}
.tcf-head h3{font-size:14px;font-weight:800;color:#3b0764}
.tcf-controls{display:flex;gap:18px;margin-bottom:14px;flex-wrap:wrap}
.tcf-controls label{display:flex;flex-direction:column;gap:4px;font-size:11px;font-weight:700;color:#7c3aed}
.tcf-controls input{width:80px;border:1.5px solid #e9d5ff;border-radius:7px;padding:5px 8px;font-size:12.5px;font-family:inherit;color:#3b0764}
.tcf-controls input:focus{outline:none;border-color:#a855f7}
.tcf-table{width:100%;border-collapse:collapse;font-size:12.5px}
.tcf-table th{text-align:left;padding:8px 10px;color:#8b5cf6;font-weight:700;font-size:10.5px;text-transform:uppercase;letter-spacing:.03em;border-bottom:1.5px solid #ede4fb}
.tcf-table th:not(:first-child){text-align:center}
.tcf-table td{padding:0;border-bottom:1px solid #f5f0fc}
.tcf-table td:first-child{padding:9px 10px;font-weight:700;color:#3b0764}
.tcf-cell{display:block;text-align:center;padding:9px 6px;font-variant-numeric:tabular-nums;font-weight:700;transition:background .12s,color .12s}var COLUMNS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun'];
var REPS = [
{ name: 'Priya Nair', jan: 62, feb: 71, mar: 45, apr: 88, may: 92, jun: 78 },
{ name: 'Marcus Webb', jan: 30, feb: 28, mar: 35, apr: 41, may: 38, jun: 44 },
{ name: 'Yuki Tanaka', jan: 95, feb: 101, mar: 87, apr: 76, may: 68, jun: 90 },
{ name: 'Elena Cruz', jan: 55, feb: 60, mar: 58, apr: 33, may: 47, jun: 62 },
{ name: 'Dan Osei', jan: 18, feb: 22, mar: 25, apr: 20, may: 30, jun: 27 },
];
var lowInput = document.getElementById('tcfLow');
var highInput = document.getElementById('tcfHigh');
var body = document.getElementById('tcfBody');
// A real function mapping a value to a color band based on the current threshold controls.
function colorFor(value, low, high) {
if (value < low) return { bg: '#fee2e2', fg: '#b91c1c' };
if (value < high) return { bg: '#fef3c7', fg: '#92400e' };
return { bg: '#dcfce7', fg: '#15803d' };
}
function render() {
var low = Number(lowInput.value);
var high = Number(highInput.value);
if (high < low) high = low;
body.innerHTML = REPS.map(function (rep) {
var cells = COLUMNS.map(function (key) {
var value = rep[key];
var color = colorFor(value, low, high);
return '<td><span class="tcf-cell" style="background:' + color.bg + ';color:' + color.fg + '">' + value + '</span></td>';
}).join('');
return '<tr><td>' + rep.name + '</td>' + cells + '</tr>';
}).join('');
}
lowInput.addEventListener('input', render);
highInput.addEventListener('input', render);
render();Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Conditional Formatting Table — Cells Colored Live From Adjustable Thresholds

Spreadsheets have offered conditional formatting for decades because color is the fastest way to scan a grid of numbers for outliers — red for underperforming, green for exceeding target — without reading every value individually. This snippet reimplements that as a genuine live computation: a plain function maps each cell's numeric value to a color band based on two adjustable threshold inputs, and every cell recolors immediately whenever those thresholds change.
A pure value-to-color function, not precomputed classes
colorFor(value, low, high) takes a number and the two current threshold values and returns a background/foreground color pair with three plain if/return branches — below low is red, between low and high is amber, at or above high is green. Nothing about which cells are which color is baked into the markup or the data; every single cell's color is the live output of calling this function during render, which is what makes the thresholds genuinely adjustable rather than cosmetic.
Number inputs drive a full re-render
Two <input type="number"> controls hold the current low and high thresholds. Their input event (firing on every keystroke or spinner click, not just on blur) calls render(), which re-runs colorFor against every cell in the table with the new threshold values and rewrites the table body — so dragging or typing a new threshold recolors the entire grid live, the same way dragging a conditional formatting rule's threshold in a spreadsheet does.
A guard against an inverted range
If a user sets the high threshold below the low threshold, the three-band logic would produce a nonsensical or empty middle band. render() clamps high up to at least low before computing colors, so the bands always stay in a sane low ≤ middle < high order regardless of what the user types into the controls.
Style applied inline from computed values, not toggled classes
Because the color bands are threshold-driven rather than a small fixed set of states, each cell's background and text color are set directly via an inline style attribute built from colorFor's return value, rather than switching between a few predefined CSS classes — the right approach when the underlying value-to-color mapping is a continuous function of user-adjustable numbers rather than a small enum of fixed states.
Same function, every column
render() runs the identical colorFor call across every numeric column for every rep — there's no per-column special-casing, so adding a seventh month of data or changing which columns exist requires no changes to the coloring logic at all, only to the COLUMNS array driving which fields get rendered.
Customizing it
Add a third or fourth band with more granular thresholds, invert the direction (lower is better) with a toggle, or compute thresholds automatically from the dataset's own mean and standard deviation instead of fixed inputs. Pair with a multi-column sort table to sort by whichever month is currently most colored.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA sales rep performance table renders with cells colored red, amber, or green by value.
- 2Adjust the Low threshold inputEvery cell below the new low value immediately turns red; the table recolors live.
- 3Adjust the High threshold inputCells at or above the new high value turn green; values between the two turn amber.
- 4Set High below LowThe high value automatically clamps up to match low, keeping the three bands sane.
- 5Scan for outliersColors make it immediately obvious which reps are under or over target across months.
- 6Swap in your own data and columnsReplace REPS and COLUMNS — colorFor runs identically across every numeric column.
Real-world uses
Common Use Cases
colorFor function that maps a value and two thresholds to a colour band.Got questions?
Frequently Asked Questions
The color bands are driven by two continuously adjustable number inputs, not a small fixed set of states — colorFor computes an actual color value based on where a number falls between two arbitrary thresholds a user can change at any time. Toggling between a handful of predefined CSS classes works for a fixed number of states, but a threshold-driven function is more directly expressed by computing the style value itself in JavaScript and applying it inline.
Add another comparison branch to colorFor before the final return, e.g. if (value >= high * 1.5) return { bg: '#a7f3d0', fg: '#065f46' } for an "exceptional" band, checked before the regular at-or-above-high case — the render loop and every input control keep working unchanged since they only ever call colorFor and use whatever it returns.
Swap which branch returns red and which returns green in colorFor — the below-low branch would return the green color pair and the at-or-above-high branch would return red, since the function only decides which color a range gets, not which direction is "good." Everything else (the threshold inputs, the render loop) is unaffected.
Before rendering, calculate the dataset's mean and standard deviation (or a percentile) across all visible values, and set low/high to something like mean minus one standard deviation and mean plus one standard deviation instead of reading them from the input fields — colorFor itself doesn't need to change, since it only needs any two numeric thresholds passed in.
Keep colorFor as a pure, framework-independent function. Store the low and high threshold values in component state bound to two number inputs, and compute each cell's inline style (or CSS custom properties) by calling colorFor(value, low, high) during render — the function itself needs no changes since it takes plain numbers and returns a plain color pair.