I know how to make a <td>
cell background color reflect the value in the enclosed <input type=number>
field using JavaScript (below is a snippet to do this). But is there a way to accomplish this without JavaScript? Using only CSS? I have full control over CSS, but not JavaScript. I can assume bleeding-edge browser versions for the latest CSS features.
function updateBgColor(td) {
const input = td.getElementsByTagName('input')[0]
td.style.backgroundColor = `rgb(96,${128+input.value*127},96)`
}
for (const input of document.getElementsByTagName('input')) {
input.addEventListener('input', event => updateBgColor(event.target.parentElement))
}
[...document.getElementsByTagName('td')].forEach(updateBgColor)
input[type=number] {
max-width: 6em;
background-color: transparent;
}
<table>
<tr>
<td><input type=number value="0.9097174053213968"></td>
<td><input type=number value="0.8408987079229189"></td>
<td><input type=number value="0.7024472413860736"></td>
<td><input type=number value="0.5185149759852405"></td>
</tr>
<tr>
<td><input type=number value="0.51746948952139"></td>
<td><input type=number value="0.23249116318958274"></td>
<td><input type=number value="0.24954372649914003"></td>
<td><input type=number value="0.7852880719259674"></td>
</tr>
<tr>
<td><input type=number value="0.006423826937380195"></td>
<td><input type=number value="0.18798660892180274"></td>
<td><input type=number value="0.33787422244115817"></td>
<td><input type=number value="0.2012761039221167"></td>
</tr>
<tr>
<td><input type=number value="0.6635851618352506"></td>
<td><input type=number value="0.4795899245078339"></td>
<td><input type=number value="0.6457512930088372"></td>
<td><input type=number value="0.6226533953736908"></td>
</tr>
</table>
This is possible, but do be advised that it is (very) ugly, and onerous.
To achieve the required result we can take advantage of the [attribute^="value"]
notation, but this will – depending on the required precision – increase the size, and complexity, of your CSS. A simple example is below:
// simple utility functions to reduce typing, and simplify the use of
// Array methods with NodeLists, by converting them to Arrays:
const D = document,
create = (tag, props) => Object.assign(D.createElement(tag), props),
fragment = () => D.createDocumentFragment(),
get = (selector, context = D) => context.querySelector(selector),
getAll = (selector, context = D) => [...context.querySelectorAll(selector)];
/*
function below taken from Matthias OTT's post:
https://matthiasott.com/notes/detecting-css-selector-support-with-javascript
*/
const isSelectorSupported = (selector) => {
try {
document.querySelector(selector)
return true
} catch (error) {
return false
}
};
let h = 0,
s = 60,
l = 65;
// here we set the text of the relevant element to represent the browser's claims of
// support for the :has() selector:
get('aside span.claimsSupport').textContent = isSelectorSupported('td:has(input[value="0.5"])') ? 'yes' : 'no';
// we retrieve the <button> element, and bind the anonymous function as the
// event-handler for the 'click' event:
get('button').addEventListener('click',
(e) => {
// retrieving the <span> element within the <button> (the e.currentTarget node):
let toggleSwitch = get('span[data-use]', e.currentTarget);
// updating its dataset.use property/data-use attribute; switchin between
// 'JavaScript' and 'CSS', to indicate what the <button> does/will do:
toggleSwitch.dataset.use = toggleSwitch.dataset.use === 'JavaScript' ? 'CSS' : 'JavaScript';
// retrieving the <table> element:
let table = get('table');
// using the Element.classList API to toggle the 'js' class on/off:
table.classList.toggle('js');
// if the <table> currently has the 'js' class:
if (table.classList.contains('js')) {
// we create an (empty) Array of 10, using Array.from():
Array.from({
length: 10
// iterate over that created Array using Array.prototype.forEach():
}).forEach(
// pass the current Array index to the function body:
(_, i) => {
// determine whether the index is odd or even:
let isOdd = i % 2 === 0,
// and use a template literal to construct a hsl() color string, with
// CSS color level 4 syntax; the use of 'isOdd' and conditional operators
// is to provide a little more variation between colors; though this
// would have been better in an Array of colors, probably:
hsl = `hsl( ${i * 36}deg ${s + (isOdd ? 10 : -10)}% ${l + (isOdd ? -10 : 10)}% / 1)`;
// we then get the elements that match the selector, so <td> elements that
// have <input> elements whose value attribute starts with a string of
// "0.i" where "i" is the current index:
getAll(`td:has(input[value^="0.${i}"`).forEach(
// we then iterate over that Array of elements, and pass a reference
// to the current element ('el') to the function body, and update its
// background-color to the string we created:
(el) => el.style.backgroundColor = hsl
);
});
// if the <table> does not have the 'js' class:
} else {
// we remove the <style> attribute to remove the JS-generated colors:
getAll('td', table).forEach((el) => el.removeAttribute('style'));
}
});
/*
*/
/* CSS custom properties used to share certain property-values across
multiple elements in the document: */
:root {
--inputInlineSize: 6rem;
--spacing: 0.55rem;
}
/* simple reset, to remove default margins and padding, and to ensure
that all elements are sized to include their borders and padding within
their declared sizes: */
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-block-size: 100vh;
padding: var(--spacing);
}
main {
/* the default color of the variable is below, this is used within
the background-image (next): */
--resultColor: lightpink;
/* using a linear gradient to visually indicate whether the
current browser supports the use of :has() along with
attribute-selectors; the default indicates false: */
background-image: linear-gradient(to bottom right, lightskyblue, transparent, var(--resultColor));
border: 2px solid currentColor;
inline-size: clamp(40rem, 80%, 1200px);
margin-inline: auto;
padding: var(--spacing);
}
section {
text-align: center;
}
button {
line-height: 1.5;
display: inline-block;
margin-block: var(--spacing);
padding-block: var(--spacing);
padding-inline: var(--spacing);
}
button::first-letter {
text-transform: uppercase;
}
button span::after {
content: attr(data-use);
}
table {
border-collapse: collapse;
margin-inline: auto;
}
td {
border: 1px solid currentColor;
padding-block: min(0.5rem, var(--spacing));
padding-inline: var(--spacing);
}
/* the following rules are responsible for styling the background-color
of the <td> elements; here we select all <td> elements that are not
the descendant of a <table class="js"> element, and which contain
an <input> with a value starting with "0.n", where "n" is an integer
in the range of 0-9: */
table:not(.js) td:has(input[value^="0.0"]) {
background-color: hsl(0deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.1"]) {
background-color: hsl(36deg 50% 75% / 1);
}
table:not(.js) td:has(input[value^="0.2"]) {
background-color: hsl(72deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.3"]) {
background-color: hsl(108deg 50% 75% / 1);
}
table:not(.js) td:has(input[value^="0.4"]) {
background-color: hsl(144deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.5"]) {
background-color: hsl(180deg 50% 75% / 1);
}
table:not(.js) td:has(input[value^="0.6"]) {
background-color: hsl(216deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.7"]) {
background-color: hsl(252deg 50% 75% / 1);
}
table:not(.js) td:has(input[value^="0.8"]) {
background-color: hsl(288deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.9"]) {
background-color: hsl(324deg 50% 75% / 1);
}
input {
inline-size: var(--inputInlineSize);
padding-block: 0.2rem;
text-indent: var(--spacing);
}
/* here we're testing the browser to see if it claims
support of the selector written within the
selector() function: */
@supports selector(td:has(input[value^="0.1"])) {
/* if so, we then update the --resultColor property-value,
which is reflected in the <main> element's background
image; do remember that the browser claiming support
does not mean that the browser has any guaranteed level
of support for the given selector: */
main {
--resultColor: palegreen;
}
}
<main>
<aside>
<p>Does your browser claim to support <code>:has()</code> selector: <span class="claimsSupport"></span></p>
<p>If, after pressing the <button> below, there is no change to the <table> presentation, then it probably does; otherwise, it may still be unsupported, partially supported, badly supported, or behind a flag.</p>
</aside>
<section>
<button>colour <table> with <span data-use="JavaScript"></span></button>
<table>
<tbody>
<tr>
<td><input type="number" value="0.91"></td>
<td><input type="number" value="0.84"></td>
<td><input type="number" value="0.70"></td>
<td><input type="number" value="0.52"></td>
</tr>
<tr>
<td><input type="number" value="0.52"></td>
<td><input type="number" value="0.23"></td>
<td><input type="number" value="0.25"></td>
<td><input type="number" value="0.79"></td>
</tr>
<tr>
<td><input type="number" value="0.01"></td>
<td><input type="number" value="0.19"></td>
<td><input type="number" value="0.34"></td>
<td><input type="number" value="0.20"></td>
</tr>
<tr>
<td><input type="number" value="0.66"></td>
<td><input type="number" value="0.48"></td>
<td><input type="number" value="0.65"></td>
<td><input type="number" value="0.62"></td>
</tr>
</tbody>
</table>
</section>
</main>
With reference to the CSS required to style a cell based on a decimal value to one single digit, below:
table:not(.js) td:has(input[value^="0.0"]) {
background-color: hsl(0deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.1"]) {
background-color: hsl(36deg 50% 75% / 1);
}
table:not(.js) td:has(input[value^="0.2"]) {
background-color: hsl(72deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.3"]) {
background-color: hsl(108deg 50% 75% / 1);
}
table:not(.js) td:has(input[value^="0.4"]) {
background-color: hsl(144deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.5"]) {
background-color: hsl(180deg 50% 75% / 1);
}
table:not(.js) td:has(input[value^="0.6"]) {
background-color: hsl(216deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.7"]) {
background-color: hsl(252deg 50% 75% / 1);
}
table:not(.js) td:has(input[value^="0.8"]) {
background-color: hsl(288deg 70% 55% / 1);
}
table:not(.js) td:has(input[value^="0.9"]) {
background-color: hsl(324deg 50% 75% / 1);
}
We have ten rulesets, if you wish to extend that to two decimal places – to include both tenths and hundredths – that will require a hundred rules. To move beyond that, to three decimal places – tenths, hundredths, and thousandths – would require a thousand rules.
With every degree of precision we're increasing the number of rules to the power n, where n is the number of decimal places required. This would lead to a very large CSS stylesheet. Ultimately, while this is possible, it's important to remember the – paraphrased – words of the illustrious, though sadly fictional, Dr. Ian Malcolm:
Just because you can do something, doesn't mean you should do something.
References: