2

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

KyleMit
  • 30,350
  • 66
  • 462
  • 664

3 Answers3

5

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.

KyleMit
  • 30,350
  • 66
  • 462
  • 664
1

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

WebDevBooster
  • 14,674
  • 9
  • 66
  • 70
  • 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 Feb 16 '18 at 23:11
  • By clicking those labels the corresponding (hidden) checkbox or radio input gets checked/unchecked. – WebDevBooster Feb 16 '18 at 23:12
0

The v4 version seems to be working fine in Firefox:

<link href="https://maxcdn.bootstrapcdn.com/bootswatch/4.0.0-beta.3/materia/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>
Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
  • Hey Michael, are you sure? Might be different FF versions, but I just went directly to https://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 – KyleMit Feb 17 '18 at 01:29
  • @KyleMit Yes; I checked that on FF 58. – Michał Perłakowski Feb 17 '18 at 01:37
  • Ok, perhaps it's just a version thing - doesn't seem to [work in 52](https://i.imgur.com/V4ZTniu.png), 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! – KyleMit Feb 17 '18 at 01:41