Search code examples
htmlcssradio-button

How to style the parent label of a checked radio input


I need to stylize some radio inputs. I tried some solutions from here but none worked for me. Can someone please take a look at this code and tell me what can I do?

This is the HTML:

<div class="controls">
  <table>
    <tbody>
      <tr>
        <td>
          <label class="radio">
            <input type="radio" name="ad_caroserie" value="0">Berlina
          </label>
        </td>
        <td>
          <label class="radio">
            <input type="radio" name="ad_caroserie" value="1">Break
          </label>
        </td>
        <td>
          <label class="radio">
            <input type="radio" name="ad_caroserie" value="2">Cabrio
          </label>
        </td>
      </tr>
    </tbody>
  </table>
</div>

And the CSS:

label.radio {
  background: #fcb608;
}

.radio input {
  display: none;
}

label.radio input[type="radio"]:checked + label {
  background: #000 !important;
  border: 1px solid green;
  padding: 2px 10px;
}

The CSS doesn't have the desired effect; Can you please help me?

This is some related excerpts of JS:

// If checkboxes or radio buttons, special treatment

  else if (jQ('input[name="'+parentname+'"]').is(':radio')  || jQ('input[name="'+parentname+'[]"]').is(':checkbox')) {
    var find = false;
    var allVals = [];
    jQ("input:checked").each(function() {
      for(var i = 0; i < parentvalues.length; i++) {
        if (jQ(this).val() == parentvalues[i] && find == false) {  
          jQ('#adminForm #f'+child).show();
          jQ('#adminForm #row_'+child).show();
          find = true;
        }
      }
    });
    if (find == false) {
      jQ('#adminForm #f'+child).hide();
      jQ('#adminForm #row_'+child).hide();
      //cleanup child field 
      if (jQ('#adminForm #f'+child).is(':checkbox') || jQ('#adminForm #f'+child).is(':radio')) {
        jQ('#adminForm #f'+child).attr('checked', false);
      }
      else {
        if (cleanValue == true) {
          jQ('#adminForm #f'+child).val('');
        }
      }
    }
  }
  else {
    var find = false;
    for(var i = 0; i < parentvalues.length; i++) {
      if (jQ('#adminForm #f'+parentname).val() == parentvalues[i] && find == false) {  
        jQ('#adminForm #f'+child).show();
        jQ('#adminForm #row_'+child).show();
        find = true;
      }
    }
    if(find == false) {
      jQ('#adminForm #f'+child).hide();
      jQ('#adminForm #row_'+child).hide();
      // cleanup child field 
      if (jQ('#adminForm #f'+child).is(':checkbox') || jQ('#adminForm #f'+child).is(':radio')) {
        jQ('#adminForm #f'+child).attr('checked', false);
      }
      else {
        if (cleanValue == true) {
          jQ('#adminForm #f'+child).val('');
        }
      }
    }
  }
}

function dependency(child,parentname,parentvalue) {
  var parentvalues = parentvalue.split(",");
  // if checkboxes
  jQ('input[name="'+parentname+'[]"]').change(function() {
    checkdependency(child,parentname,parentvalues,true);
    //if checkboxes
    jQ('input[name="'+child+'[]"]').change();
    jQ('input[name="'+child+'"]').change();
    jQ('#'+child).change();
  });
  // if buttons radio
  jQ('input[name="'+parentname+'"]').change(function() {
    checkdependency(child,parentname,parentvalues,true);
    // if checkboxes
    jQ('input[name="'+child+'[]"]').change();
    jQ('input[name="'+child+'"]').change();
    jQ('#'+child).change();
  });
  jQ('#f'+parentname).click(function() {
    checkdependency(child,parentname,parentvalues,true);
    // if checkboxes
    jQ('input[name="'+child+'[]"]').change();
    jQ('input[name="'+child+'"]').change();
    jQ('#f'+child).change();
  });
  checkdependency(child,parentname,parentvalues,false);
}

Solution

  • A possibility

    At my time of posting, I am not exactly sure what the desired layout should be, but there is one specific problem in the attempted CSS that needs to be addressed.

    The adjacent siblings selector:

    ... separates two selectors and matches the second element only if it immediately follows the first element.

    If the <input> is a child of the <label>, it isn't adjacent, so while:

    label.radio input[type="radio"]:checked + label
    

    is looking for a label immediately following a :checked input inside a label with the class .radio, nothing like that exists.

    To alter the styling of the label in this case, would require a selector that affected the parent, which currently isn't possible.

    So, to select the label of the :checked input, we need the label to be adjacent, not the parent.

    We can use the for="id" attribute:

    A <label> can be associated with a control either by placing the control element inside the <label> element, or by using the for attribute.

    As I said, I'm not exactly sure what the desired layout should be, but here's an example using the for attribute, that doesn't look too bad.

    div {
      display: inline-block;
      position: relative;
    }
    label {
      background: #fcb608;
      padding: 2px 10px 2px 1.5em;
      border: 1px solid transparent; /* keeps layout from jumping */
    }
    input {
      position: absolute;
    }
    input[type="radio"]:checked + label {
      background: #000;
      border-color: green;
      color: white;
    }
    <div>
      <input id="id1" type="radio" name="ad_caroserie" value="0">
      <label for="id1" class="radio">Berlina</label>
    </div>
    <div>
      <input id="id2" type="radio" name="ad_caroserie" value="1">
      <label for="id2" class="radio">Break</label>
    </div>
    <div>
      <input id="id3" type="radio" name="ad_caroserie" value="2">
      <label for="id3" class="radio">Cabrio</label>
    </div>

    With <input> as a child of <label>

    Using a small JavaScript handler listening for changes to the <form>.

    • If a change is detected, the triggered function checks if an <input type="radio"> was changed, and if so, if it has a <label> as its parentElement.
    • If that's true, it checks to see if there's an identically named <input type="radio"> that's a child of a <label> element with the class .checked.
    • If there is, it removes the class from the <label> before applying the same class to the <label> parent of the <input> target that triggered the whole thing.

    let form = document.querySelector( "form" );
    
    form.addEventListener( "change", ( evt ) => {
      let trg = evt.target,
          trg_par = trg.parentElement;
      
      if ( trg.type === "radio" && trg_par &&
           trg_par.tagName.toLowerCase() === "label" ) {
        
        let prior = form.querySelector( 'label.checked input[name="' +
                                        trg.name + '"]' );
        
        if ( prior ) {
          prior.parentElement.classList.remove( "checked" );
        }
        
        trg_par.classList.add( "checked" );
        
      }
    }, false );
    label {
      background: #fcb608;
      padding: 2px 10px 2px 0;
      border: 1px solid transparent; /* keeps layout from jumping */
    }
    label.checked {
      background: #000;
      border-color: green;
      color: white;
    }
    <form>
      <label class="radio"><input type="radio" name="ad_caroserie" value="0">Berlina</label>
      <label class="radio"><input type="radio" name="ad_caroserie" value="1">Break</label>
      <label class="radio"><input type="radio" name="ad_caroserie" value="2">Cabrio</label>
    </form>

    Without JavaScript things get difficult (per my original explanation of why it's best to use the for attribute in this case).

    We can use the appearance property (with prefixes and reasonable support) to effectively hide the user-agent radio GUI, then use the remaining faceless element to build a fake background for the <label>.

    This is very hacky and a great deal less dynamic than the default, since some absolute positioning and specific dimensions are required to pull it off.
    It kind of works (in most browsers), but is tricky to enforce sitewide.

    Something to play around with though :-)

    input {
      position: absolute;
      -webkit-appearance: none;
      -moz-appearance: none;
      appearance: none;
      width: 5em;
      height: 1.5em;
      z-index: -1;
      background: #fcb608;
      border: 1px solid transparent;
      margin: -.1em -.8em;
      outline: 0;
    }
    label {
      display: inline-block;
      width: 5em;
      color: white;
      text-shadow: 1px 1px 0px black;
    }
    input[type="radio"]:checked {
      background: #000;
      border-color: green;
    }
    <label class="radio"><input type="radio" name="ad_caroserie" value="0">Berlina</label>
    <label class="radio"><input type="radio" name="ad_caroserie" value="1">Break</label>
    <label class="radio"><input type="radio" name="ad_caroserie" value="2">Cabrio</label>