skip to Main Content

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>

2

Answers


  1. The short answer is no.

    CSS selectors can access many things about elements, their attributes, relationship with other elements in the DOM, and the position of text content within elements. What it doesn’t do is provide selectors based on some or all of the .textContent property of an element in the DOM.

    For example, CSS rules for the first line of an element using the pseudo element ::firstline selector apply to text content identified by being rendered as the first line in layout, not by what the text rendered (on the first line) actually says.

    CSS Standard Refererence without third party interpretation: W3C Selectors Level 3

    Login or Signup to reply.
  2. 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 &lt;button&gt; below, there is no change to the &lt;table&gt; presentation, then it probably does; otherwise, it may still be unsupported, partially supported, badly supported, or behind a flag.</p>
      </aside>
      <section>
        <button>colour &lt;table&gt; 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>

    JS Fiddle demo.

    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:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search