There is a :checked
pseudo class in CSS. I often think of it in connection with the “checkbox hack”, in which you use it on a hidden checkbox with the ~ general sibling combinator to simulate toggling behavior without any JavaScript. It’s a hack because now you have these stray form elements on the page that really aren’t for a form. Not a huge deal, as I’m sure you can work around it accessibility wise, but there is a way to use this same concept in a totally non-hacky way, on an actual form!
I used this technique on the CodePen job posting form to only reveal additional form fields as needed.
Let’s get to the point with a demo:
See the Pen LEGXOK by Chris Coyier (@chriscoyier) on CodePen.
The HTML
Let’s say we’re using radio buttons here. Either a checkbox or a radio button can be “checked” (:checked). The div we’re going to reveal has to be after it, and a sibling element (within the same parent).
<input type="radio" name="choice-animals" id="choice-animals-dogs">
<label for="choice-animals-dogs">I like dogs more</label>
<div class="reveal-if-active">
Anything you want in here.
</div>
The CSS
By default, that “reveal-if-active” div will be hidden. We’re going to do that in a number of different ways. They aren’t all required just to hide it, but they will play a factor in how it reveals itself.
.reveal-if-active {
opacity: 0;
max-height: 0;
overflow: hidden;
}
Now when a radio button or checkbox at the same level as it becomes checked, it reveals:
input[type="radio"]:checked ~ .reveal-if-active,
input[type="checkbox"]:checked ~ .reveal-if-active {
opacity: 1;
max-height: 100px; /* little bit of a magic number :( */
overflow: visible;
}
That works, but let’s transition everything fancy style, and Sass it up:
.reveal-if-active {
opacity: 0;
max-height: 0;
overflow: hidden;
transform: scale(0.8);
transition: 0.5s;
input[type="radio"]:checked ~ &,
input[type="checkbox"]:checked ~ & {
opacity: 1;
max-height: 100px;
overflow: visible;
padding: 10px 20px;
transform: scale(1);
}
}
That’ll do the trick.
What about required fields?
This gets a little tricker. Let’s say in the “reveal-if-active” divs you’re putting more form fields (that’s the whole point really). You might want to require a field if it’s revealed, but not if it’s not. A required field that is hidden is still required, and so you can’t just put the required
attribute on those hidden fields and forget about, lest get yourself in a nasty UX situation.
I handle this by putting a “require-if-active” class on the input, and a data-*
attribute which references the ID it’s paired with.
...
<div class="reveal-if-active">
<label for="which-dog">Good call. What's the name of your favorite dog?</label>
<input class="require-if-active" type="text" id="which-dog" name="which-dog" data-require-pair="#choice-animals-dogs">
</div>
Then, when the page loads, and whenever a checkbox or radio button changes value, you run some JavaScript to determine whether that input should be required or not.
var FormStuff = {
init: function() {
// kick it off once, in case the radio is already checked when the page loads
this.applyConditionalRequired();
this.bindUIActions();
},
bindUIActions: function() {
// when a radio or checkbox changes value, click or otherwise
$("input[type='radio'], input[type='checkbox']").on("change", this.applyConditionalRequired);
},
applyConditionalRequired: function() {
// find each input that may be hidden or not
$(".require-if-active").each(function() {
var el = $(this);
// find the pairing radio or checkbox
if ($(el.data("require-pair")).is(":checked")) {
// if its checked, the field should be required
el.prop("required", true);
} else {
// otherwise it should not
el.prop("required", false);
}
});
}
};
FormStuff.init();
Yep, little JavaScript required. We could do the hide/showing through JavaScript as well, but CSS is quite well suited for it. You might even say it’s a design concern, so it belongs in CSS. #debate
Here’s that demo again.
Weird Quirk
Becca Rice wrote in to tell me that there is a bug with the CSS stuff that reveals the additional fields specifically in UI WebView on iOS 9 and earlier. Not directly in Safari, but in apps that use the built in WebView to show the web within their own apps. Even, at the time of this writing, the entire Chrome app on iOS uses UI WebView.
The bug is: you can click a radio button to expand the additional fields. Maybe even click a different one to close the originals and open a new set of additional fields, but then it stops working. You can continue toggling the radios but nothing changes anymore. It’s not clear if the radio button is only visually changing state but not actually changing value, or if the CSS just isn’t reacting to the new :checked
value.
We both confirmed it. There is an app just for testing UI WebView vs. WK WebView.
Really really interesting! WoW!
Will we be able to transition to height: auto at some point in the future instead of having to transition to a certain number using max-height? It can be a pain using max-height sometimes. Depending on the number you use, it can change the duration of the transition.
Also, this is a fantastic post. I’ve been dealing with this kind of stuff lately. Really nice approach.
My guess is that yes, we will be able to eventually. Probably just like that, to
height: auto;
. The CSS spec folks think it’s a reasonable desire and something a browser should be able to do.Dynamic forms make uses nervous. Even when the form is a multiple steps form, they need to know what’s coming.
Choosing among option and then having to enter more information given the option they selected is something that most uses don’t appreciate.
If it’s a well-considered one, can be very attractive part of your web.
I use it without transition and it works great. It costs me a few hours before the final effect, but it is worth it.
What’s wrong with my “r”? I meant to say “users”
I really love what you have done with the form I am just having difficulty with inserting it into my form to email php… could you possible help me with this
Those examples are a bit broken in Android 4.4 webview.
Isn’t the ~ called a “general sibling selector”?
YES. Thanks.
Hey Chris,
In my business we actually do this, rather than JS toggle (which I banned over 2 years ago). There are some important things to note about the benefits of this over JS
Combining with WCAG & ARIA specs / recommends, you can make this much more natural for your users with accessibility needs.
Core features will work without JS, so it’s less heavy for the browser in my experience (needs more tests to prove I suppose)
If you are careful in your implementation, you can actually use this back-end to make decsions and then the radio / checkbox is not useless, it is in fact much more useful than JS alone.
Interestingly enough I had never considered crafting a solution to fit each use-case for required, but I really liked your take on using data attributes.
One thing that has been eating at me lately is the DOM to content ratio and transmission of these data attributes. I Would say implement a callback via class for onblur of anything hidden; You can then hook your flash messaging or page notifications to let users know if something is required and not filled in (maybe force focus back on it for some real usability), only when they exit the box (sneaky I know). A really downright dirty way of implementing forced focus on this without JS is to wrap the checkbox in a label where for=”id” of the hidden input field (forces focus, clicking elsewhere forces blur…)
Anyway just my musings, great article, hope you got the huge hosting bill for the DB thing sorted!
Nice :-)
Another thing that you have not mention in the article but your codePen demo is right : the hidden input must be in the HTML at the beginning and not added by some Javascript. I saw many times some Javascript adding input element on the fly : the result is that the input is lost during history traversal (back/forward buttons).
I personally prefer to use :not(:checked) to facilitate the hiding. This way in the rare circumstance :checked isn’t supported by the browser the fallback is to show too much data instead of too little. Also, I personally find it easier to style with :not(:checked) when you’re not trying to fight a possibly highly specific selector that wants to hide the element at all times.
i was just dealing with this exact thing the other day. If you disable an input, it trumps
required
. Proof of concept: http://codepen.io/anon/pen/zxqxer Submit with no values. The first input does not check for a value, the second does.Based on this, i prefer to toggle
disabled
when toggling the secondary input, leavingrequired
as-is. This provides two helpful side effects:By hiding an input, you imply you don’t want its value to be sent (exception, obviously,
[type="hidden"]
that aren’t meant for user interaction). Disabling the input when hiding it will block submission of any value set before it was hidden, even if it’s required when visible.You don’t have to add additional classes or
data-
attributes to track which inputs should be required. If they’re enabled (visible), they follow validation. If not, out of sight out of mind.Using CSS to influence the dynamic behavior of a form ?
Is this a good idea ?
Always thought CSS is used to just style elements.
Don’t see the problem. We have been influencing the behaviour of menus since the dawn of time via the :hover pseudo element.
Interesting point, I also would say that CSS becomes an half-successor for some script functions, partially for jQuery, Java…
I think it’s good, since you can focus on the project you working on, not the way you do it.
IMO checkbox-hack might not be a hack anymore.
Once you start using this method you won’t go back to JS anymore.
It’s very easy to use and you can make some really nice things with it.
I recently created an image gallery with this method.
Is it possible to show the reveal-if-active below the radio options?
Now the result has to be right underneath/next to the radio button, and then the next radio button.
Would be nice if you could have for example 3 radio buttons next to each other, and the reveal-if-active text dynamicly BELOW those three buttons.
Is this possible?