Home > Software engineering >  Change styles of radio container when radio is checked or hovered
Change styles of radio container when radio is checked or hovered

Time:10-20

I need to change the styles of 2 radio containers when the radio option has been clicked or hovered

.gchoice_1_15_0, .gchoice_1_15_1 {
    width: 48%;
    color: #58595B;
    border: 1px solid #E4E4E4;
    border-radius: 10px;
    padding-left: 1em;
    background: #fff;
    transition: all ease-in-out .3s;
    box-shadow: none;
}
//Yes option
<div class="gchoice gchoice_1_15_0">
        <input class="gfield-choice-input" name="input_15" type="radio" value="Yes" id="choice_1_15_0" onchange="/*gformToggleRadioOther( this )*/">
        <label for="choice_1_15_0" id="label_1_15_0">Yes</label>
</div>

//No option
<div class="gchoice gchoice_1_15_1">
        <input class="gfield-choice-input" name="input_15" type="radio" value="No" id="choice_1_15_1" onchange="/*gformToggleRadioOther( this )*/">
        <label for="choice_1_15_1" id="label_1_15_1">No</label>
</div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Once any of the options are checked or hovered I need to change the styles of the correct container:

If the "Yes" option is checked, change .gchoice_1_15_0 to

background: #EF8B22;
border: 1px solid #EF8B22;
box-shadow: 0px 0px 6px 0px rgb(239 139 4 / 30%);

If the "No" option is checked, change .gchoice_1_15_1 to

background: #EF8B22;
border: 1px solid #EF8B22;
box-shadow: 0px 0px 6px 0px rgb(239 139 4 / 30%);

I tried with this CSS but didn't work:

#choice_1_15_0:checked ~ .gchoice_1_15_0 {
    background: #EF8B22;
    border: 1px solid #EF8B22;
    box-shadow: 0px 0px 6px 0px rgb(239 139 4 / 30%);
}

How can I achieve that?

PD. HTML code can't be modified.

CodePudding user response:

The problem:

I tried with this CSS but didn't work:

  • It doesn't work because CSS selectors are "forward-only".

    • The ~ and combinators in CSS selectors cannot select an element based on a descendant element or next-sibling element's attributes or DOM state.
    • Conversely: It can only select elements based on their ancestor and previous-sibling elements' attributes and DOM state.
    • Indeed, the combinator doesn't mean "adjacent sibling", it means "next adjacent sibling", and ~ doesn't mean "any sibling", it means "any future siblings".
  • In your case: a CSS selector cannot use the :checked state of the <input id="choice_1_15_0" /> when selecting <div >...

    • ...but CSS can use it to select <label for="choice_1_15_0">.

CSS selectors work this way for multiple reasons, the main one thing performance: the algorithm for applying CSS rules with these restrictive forward-only rules is much simpler and faster than if it had to support any kind of "look-backwards and upwards" rules.

That said, what I'm referring to is actually supported by web-browsers: it's called the :has() relational selector, however because of those performance reasons it isn't supported in CSS stylesheets, only in in the DOM's querySeletor and querySelectorAll functions:

I note that since 2021, some (non-Google) Chrome devs are investigating ways to try to implement support for limited kinds of sub-selectors in :has() in Chrome, read about it here: https://css-has.glitch.me/ - but I doubt support for this will enter Chrome for years, let alone other browser engines like Safari and Firefox.

Ideal solution: :has() selector:

If we could use :has(), then you could just do this and call it a day:

label[for]:has(input[type=radio]:checked) {
    background: #EF8B22;
    border: 1px solid #EF8B22;
    box-shadow: 0px 0px 6px 0px rgb(239 139 4 / 30%);
}

Unfortunately they don't, so we can't do this.


Workaround: move your <input/> elements:

The workaround is to hoist up your <input/> to be located both above and before its container - but as you don't want the checkbox widget to be visually located there you'll need to either make the input invisible and use a replacement checkbox image in the <label> or use a CSS technique to relocate the rendered input element, perhaps using CSS grid or flex (or even position) but these don't give you much flexibility.

You can also use ::before to add a fake checkbox/radio button (either using an aesthetically pleasing SVG or PNG as background-image, or use CSS border to render a box directly).

Something like this:

input  { display: none; }
 
 .gchoice_1_15_0,
 .gchoice_1_15_1 {
      width: 48%;
      color: #58595B;
      border: 1px solid #E4E4E4;
      border-radius: 10px;
      padding-left: 1em;
      background: #fff;
      transition: all ease-in-out .3s;
      box-shadow: none;
}

#choice_1_15_0:checked ~ * label[for="choice_1_15_0"],
#choice_1_15_1:checked ~ * label[for="choice_1_15_1"] {
    background: #EF8B22;
    border: 1px solid #EF8B22;
    box-shadow: 0px 0px 6px 0px rgb(239 139 4 / 30%);
}

/* CSS-only radio buttons: */
label::before {
    display: inline-block;
    content: ' ';
    background: white;
    border: 1px solid black;
    border-radius: 7px;
    box-sizing: border-box;
    width: 14px;
    height: 14px;
}

#choice_1_15_0:checked ~ * label[for="choice_1_15_0"]::before,
#choice_1_15_1:checked ~ * label[for="choice_1_15_1"]::before {
    background: radial-gradient(circle, rgba(0,0,0,1) 40%, rgba(0,0,0,0) 40%);
}
<input class="gfield-choice-input" name="input_15" type="radio" value="Yes" id="choice_1_15_0" onchange="/*gformToggleRadioOther( this )*/">

<input class="gfield-choice-input" name="input_15" type="radio" value="No" id="choice_1_15_1" onchange="/*gformToggleRadioOther( this )*/">

//Yes option
<div class="gchoice gchoice_1_15_0">
   <label for="choice_1_15_0" id="label_1_15_0">Yes</label>
</div>

//No option
<div class="gchoice gchoice_1_15_1">
    <label for="choice_1_15_1" id="label_1_15_1">No</label>
</div>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related