Bootstrap Paper's material style of radio/checkbox inputs broken in IE/FF

19,981

Solution 1

The core problem faced by the implementation of the Paper Theme is it does so by styling the <input type="checkbox" /> elements using :before or :after pseudo-elements.

Per this the W3C specs and this answer in can I use a pseudo-element on an input field?

:before and :after render inside a container and <input>s can not contain elements

The fact that it works in chrome is the exception, not the rule, but we're not out of luck ...

Pure CSS Approach

So we'll want to avoid styling anything within the actual checkbox itself, but we can apply those same styles to the label instead, which will still toggle the checkbox that it describes.

Step 1 - Label Based Toggle

We do have to restructure the HTML so that checkbox comes before the label, instead of inside it. We'll do that so we can use the :checked and adjacent sibling selector + to conditionally style the label based on whether or not the checkbox is checked.

<!-- Old Format -->
<div class="checkbox">
  <label>
    <input type="checkbox" value="check1"/>
    Check Label Text
  </label>
</div>

<!-- New Format -->
<div class="label-check">
   <input type="checkbox" value="check1" id="myCheck">
   <label for="myCheck">Check Label Text</label>
</div>

Note: In order for this solution to work, you do need to make sure that you have valid HTML with unique IDs for every input and corresponding for attributes in the labels that describe them.

At this point in time, we can ditch the inputs, and style the labels with our own :before pseudo elements that look like checkboxes or radio buttons. To keep things simple for now, in step one, we can mimic the controls with the following Unicode Characters ... , , ,

Here's an bare bones version with the basic gist of the CSS to style each label conditionally:

input[type='checkbox'],[type='radio']     { display: none; }
[type='checkbox']         + label::before { content: "\2610";}
[type='checkbox']:checked + label::before { content: "\2611";}
[type='radio']            + label::before { content: "\2B58";}
[type='radio']:checked    + label::before { content: "\25C9";}

We can also improve on this baseline quite a bit by adding some colorization, hovering effects, cursor properties, transition effects, and text shadows to make the unicode characters look and feel like actual buttons.

Here's a more fleshed out demo in jsFiddle and Stack Snippets:

body {
  font-size:1.3em;
  color: #2b2b2b;
  background:white;
}
.label-check input {  display:none; }

.label-check label::before {
  width: 1.4em;
  text-align: center;
  display: inline-block;
  cursor: pointer;
  color: black;
  transition: color .3s ease;
  text-shadow: 0px 0px 1px #cccccc;
}
.label-check label:hover::before {
  text-shadow: 0px 0px 1px #6286d0;
}
.label-check [type='checkbox']:checked + label::before,
.label-check [type='radio']:checked    + label::before{ 
  color: #056dce;
}

.label-check [type='checkbox']         + label::before { content: "\2610";}
.label-check [type='checkbox']:checked + label::before { content: "\2611";}
.label-check [type='radio']            + label::before { content: "\2B58";}
.label-check [type='radio']:checked    + label::before { content: "\25C9";}
<h3>
  Inputs... we don't need no stinkin Inputs
</h3>

<div class="label-check">
  <div>
    <input type="checkbox" name="checkGrp1" id="check1_Opt1">
    <label for="check1_Opt1">A Label Here 1</label>
  </div>
  <div>
    <input type="checkbox" name="checkGrp1" id="check1_Opt2" checked>
    <label for="check1_Opt2">A Label Here 2</label>
  </div>
</div>

<div class="label-check">
  <div>
    <input type="radio" name="radioGrp1" id="radio1_Opt1">
    <label for="radio1_Opt1">Radio Label 1</label>
  </div>
  <div>
    <input type="radio" name="radioGrp1" id="radio1_Opt2" checked>
    <label for="radio1_Opt2">Radio Label 2</label>
  </div>  
</div>

Step 2 - Apply Material Style to label:before with CSS

With the concept of using a the label's stylized pseudo elements as a toggle in play, we can apply Paper's styles / CSS instead of just { content: "\2610";} to give our checkboxes a Material look and feel.

To do that, let's look at how the styles were supposed to work. We can look at the source code that describes checks and radios on Github in bootstrap.css#L7182

The :after element is being used to style the surrounding box/circle while the :before element adds the inner selection when the box is :checked.

Here are the high level styles for the checkbox:

Material Checkbox CSS

Note: The check mark is made by rotating a rectangle 45 degrees and adding a white border to the bottom and right.

And here's a high level view of the styles for the radio button:

Material Radio CSS

Note: The radio button container and inside is just a regular CSS circle (border-radius:50%) with varying sizes which can animate by scaling up or down.

So we'll migrate any pseudo elements found on the checkbox input to it's adjacent label like this:

/* old */ input[type="radio"]:before
/* new */ input[type="radio"] + label:before

After some careful conversion, here's a demo in jsFiddle and Stack Snippets:

body {
  padding: 0 25px;
  font-size: 1.2em;
}


.checkbox, .radio {
    margin: 10px;
}
.checkbox, .radio {
    position: relative;
}


.label-check label {
  padding-left: 20px;
}
.label-check input[type="radio"],
.label-check input[type="checkbox"] {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
    opacity: 0;
    position: absolute;
    margin: 0;
    z-index: -1;
    width: 0;
    height: 0;
    overflow: hidden;
    left: 0;
    pointer-events: none;
}
.label-check input[type="radio"]:focus {
  outline: none;
}
.label-check input[type="radio"] + label:before,
.label-check input[type="radio"] + label:after {
  content: "";
  display: block;
  position: absolute;
  left: -10px;
  top: 1px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  -webkit-transition: 240ms;
  -o-transition: 240ms;
  transition: 240ms;
}
.label-check input[type="radio"] + label:before {
    left: -8px;
    top: 3px;
}
.label-check input[type="radio"] + label:before {
  background-color: #2196f3;
  -webkit-transform: scale(0);
      -ms-transform: scale(0);
       -o-transform: scale(0);
          transform: scale(0);
}
.label-check input[type="radio"] + label:after{
  top: 1px;
  border: 2px solid #666666;
  z-index:1;
}
.label-check input[type="radio"]:checked + label:before {
  -webkit-transform: scale(0.6);
      -ms-transform: scale(0.6);
       -o-transform: scale(0.6);
          transform: scale(0.6);
}
.label-check input[type="radio"]:disabled:checked + label:before {
  background-color: #bbbbbb;
}
.label-check input[type="radio"]:checked + label:after {
  border-color: #2196f3;
}
.label-check input[type="radio"]:disabled + label:after,
.label-check  input[type="radio"]:disabled:checked + label:after {
  border-color: #bbbbbb;
}
.label-check input[type="checkbox"]:focus {
  outline: none;
}
.label-check input[type="checkbox"]:focus + label:after{
  border-color: #2196f3;
}
.label-check input[type="checkbox"] + label:after {
  content: "";
  position: absolute;
  top: 2px;
  left: -10px; 
  display: block;
  width: 18px;
  height: 18px;
  margin-top: -2px;
  margin-right: 5px;
  border: 2px solid #666666;
  border-radius: 2px;
  -webkit-transition: 240ms;
       -o-transition: 240ms;
          transition: 240ms;
}
.label-check input[type="checkbox"]:checked + label:before {
  content: "";
  position: absolute;
  top: 2px;
  left: -3px;
  display: table;
  width: 6px;
  height: 12px;
  border: 2px solid #fff;
  border-top-width: 0;
  border-left-width: 0;
  -webkit-transform: rotate(45deg);
      -ms-transform: rotate(45deg);
       -o-transform: rotate(45deg);
          transform: rotate(45deg);
  z-index:1;
}
.label-check input[type="checkbox"]:checked + label:after{
  background-color: #2196f3;
  border-color: #2196f3;
}
.label-check input[type="checkbox"]:disabled + label:after {
  border-color: #bbbbbb;
}
.label-check  input[type="checkbox"]:disabled:checked + label:after {
  background-color: #bbbbbb;
  border-color: transparent;
}
<h3>
 Material Label Based Checks
</h3>

<div class="label-check">
  <div class="checkbox">
    <input type="checkbox" name="checkGrp1" id="check1_Opt1">
    <label for="check1_Opt1">A Label Here 1</label>
  </div>
  <div class="checkbox">
    <input type="checkbox" name="checkGrp1" id="check1_Opt2" checked>
    <label for="check1_Opt2">A Label Here 2</label>
  </div>
</div>

<div class="label-check">
  <div class="radio">
    <input type="radio" name="radioGrp1" id="radio1_Opt1">
    <label for="radio1_Opt1">Radio Label 1</label>
  </div>
  <div class="radio">
    <input type="radio" name="radioGrp1" id="radio1_Opt2" checked>
    <label for="radio1_Opt2">Radio Label 2</label>
  </div>  
</div>

HTML/JS Approach

There are some other material implementations that have hit this target in a cross browser friendly way. All of which typically rely on:

  1. Hiding the actual <input> element
  2. Placing a stylized checkbox box inside of the label

In some cases, this relies on formatting the HTML appropriately yourself at design time, or generating it when the site initializes.

For example, FezVrasta's Material Design for Bootstrap, in both V3 and V4 will take an HTML structure like this:

<div class="checkbox">
  <label>
    <input type="checkbox"> 
    Notifications
  </label>
</div>

And insert a new span when you call $.material.init() so the result looks like this:

<div class="checkbox">
  <label>
    <input type="checkbox">
    <span class="checkbox-material">
      <span class="check"></span>
    </span>
    Notifications
  </label>
</div>

While this relies on the initialization to occur, it allows much more fine grained control of the CSS checkbox and radio button rather than shoving it all into pseudo elements.

Solution 2

Bootstrap 4 does come with a set of custom radios and checkboxes. Which means: They can be adjusted to style in any way you want.

Here's an example of a custom checkbox that comes with Bootstrap 4 by default and is designed to look identical in all browsers:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

<div class="custom-control custom-checkbox">
  <input type="checkbox" class="custom-control-input" id="customCheck1">
  <label class="custom-control-label" for="customCheck1">Check this custom checkbox</label>
</div>

And here's an example of a custom radio (check the last snippet in that answer): https://stackoverflow.com/a/48401949/8270343

(note: only colors were adjusted in that case but the shapes and/or animations can be adjusted just as well)

Reference link:

https://getbootstrap.com/docs/4.0/components/forms/#checkboxes-and-radios-1

Share:
19,981

Related videos on Youtube

KyleMit
Author by

KyleMit

I've been writing software for the last decade or so at StackOverflow, DealerPolicy, the Vermont Department of Health, code camps, meetups, and online. I'm primarily focused on web dev, react, dotnet, and azure, but always in the mood to debug something new. Favorite SO Accomplishments: 67k+ rep given away via bounties - currently 1st of all time twitter-bootstrap - 3rd person to get the Gold Badge Socratic - Asked a well-received question on 100 separate days Sportsmanship - Up vote 100 answers on questions where an answer of yours has a positive score Refiner - Edit and answer 50 questions

Updated on June 04, 2022

Comments

  • KyleMit
    KyleMit almost 2 years

    According to Google's Material guidelines for selection controls (checkboxes, radio buttons, switches), these input fields should look like this:

    Material Selection Inputs

    The Bootstrap Paper Theme for v3.x (now called Materia in v4) is styled to hit this design target

    In chrome, inputs are stylized correctly, but all other browsers merely fallback to native input controls, which disrupts the consistency of the design metaphor.

    Here's an MCVE in jsFiddle / Stack Snippets that will work / fail depending on browser:

    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/paper/bootstrap.min.css" rel="stylesheet"/>
    
    
    <div class="checkbox">
      <label><input type="checkbox" value="check1"/> Check Opt 1 </label>
    </div>
    <div class="checkbox">
      <label><input type="checkbox" value="check2" checked/> Check Opt 2</label>
    </div>
    
    <div class="radio">
      <label><input type="radio" value="radio1" name="grp1" />Radio Opt 1</label>
    </div>
    <div class="radio">
      <label><input type="radio" value="radio2" name="grp1" checked/>Radio Opt 2</label>
    </div>

    And here how the previous code fares in each browser:

    Browser Compatability

    Is there a possible workaround for this or are we at the mercy of the browser to style inputs how they deem fit?

    This question was also asked on the project's issues page in github issue #497, but closed citing browser incompatibility

  • WebDevBooster
    WebDevBooster about 6 years
    To avoid the inconsistencies of different browsers, the .custom-radio and .custom-checkbox classes in Bootstrap 4 don't actually use the HTML input elements i.e. those are hidden with css. The .custom-control-label classes (with pseudo elements) are then used to create checkboxes and radios that look identical in all browsers.
  • WebDevBooster
    WebDevBooster about 6 years
    By clicking those labels the corresponding (hidden) checkbox or radio input gets checked/unchecked.
  • KyleMit
    KyleMit about 6 years
    Hey Michael, are you sure? Might be different FF versions, but I just went directly to bootswatch.com/materia/#forms and it didn't work in FF, IE, or Edge. I did wind up digging into an explanation and couple possible workarounds though
  • Michał Perłakowski
    Michał Perłakowski about 6 years
    @KyleMit Yes; I checked that on FF 58.
  • KyleMit
    KyleMit about 6 years
    Ok, perhaps it's just a version thing - doesn't seem to work in 52, but that's pretty dated at this point. Certainly for a robustly cross browser approach, the current design in Paper V3 & V4 look dubious per the W3C spec, but thanks for the input and testing against a new FF!