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>
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.
Advertisement
Answer
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“.
- The
In your case: a CSS selector cannot use the
:checked
state of the<input id="choice_1_15_0" />
when selecting<div class="gchoice gchoice_1_15_0">
…- …but CSS can use it to select
<label for="choice_1_15_0">
.
- …but CSS can use it to select
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>