Exposing Additional Form Fields via Checked Radio Buttons

Avatar of Chris Coyier
Chris Coyier on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

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.