Hello fellow people of Forrst! Today, I want to show you a bit of code that was recently merged into Foundation 4 from a very helpful contributor, named ghinda. He's also a member of Forrst, so give him some props if you get a chance: forrst.com/people/….

One of the new UI elements we're adding into Foundation 4 is Radio Switches. These are used commonly on iOS and other sites across the web and literally just represent a radio input element. The neat thing about this code is that it is written completely mobile-first, meaning devices that don't support media queries will get normal radio elements.

I included the Scss snippet and a sample HTML page that you can use to test this out quickly.

Here's the feedback I'm looking for:

  • Is the code as efficient as it could be from an Scss point of view?
  • Are there any options you think are missing from this UI element?
  • What do you think of using button styles to help style these?
    • Should we have unique style just for these elements instead?

The Scss

Here's the Scss used to create the Switch styles. Note: we've included functions and variables at the top of this file in order to make it easily used standalone.

@media only screen {

  // Function for calculating em values
  @function emCalc($pxWidth) {
    @return $pxWidth / $emBase * 1em;
  }

  // Global variables pulled in to make this work standalone.
  $emBase: 16px;

  // Temporarily pulled in Button Variables to make this work standalone.
  $button-med: emCalc(10px) !default;
  $button-tny: emCalc(5px) !default;
  $button-sml: emCalc(7px) !default;
  $button-lrg: emCalc(15px) !default;

  // Class names for the multiple states
  $switchStates: 3, 4, 5;

  // Generate the class names for the multiple states
  $switchClasses: ();
  @each $switch in $switchStates {
    $switchClasses: join($switchClasses, unquote(".switch-#{$switch}"), comma);
  }

  $switchClasses: join(unquote(".switch"), $switchClasses, comma);

  // Two-state switch and shared styles between all states
  #{$switchClasses} {
    position: relative;
    padding: 0;

    display: block;
    width: 100%;
    overflow: hidden;

    // Sizes
    &.large label {
      padding: $button-lrg 0;
      font-size: emCalc(17px);
    }
    &.small label {
      font-size: emCalc(12px);
      padding: $button-sml 0;
    }
    &.tiny label {
      font-size: emCalc(11px);
      padding: $button-tny 0;
    }

    label {
      position: relative;
      z-index: 2;

      float: left;
      width: 50%;
      height: 100%;
      margin: 0;
      padding: $button-med 0;

      font-size: emCalc(14px);
      font-weight: bold;
      text-align: center;
    }

    .slide-button {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 1;

      display: block;
      width: 50%;
      height: 100%;
      padding: 0;

      -webkit-transition: all .1s ease-out;
      -moz-transition: all .1s ease-out;
      transition: all .1s ease-out;
    }

    input {
      position: absolute;
      opacity: 0;
    }

    // Outline the toggles when the inputs are focused
    input:focus + label {
      outline: 1px dotted #888;
    }

    input:last-of-type:checked ~ .slide-button {
      left: 50%;
    }

    // Bugfix for older Webkit, including mobile Webkit. Adapted from:
    // http://css-tricks.com/webkit-sibling-bug/
    -webkit-animation: webkitSiblingBugfix infinite 1s;

  }

  @-webkit-keyframes webkitSiblingBugfix { from { position: relative; } to { position: relative; } }

  // Generate styles for the multiple states
  @for $i from 1 through length($switchStates) {
    $state: nth($switchStates, $i);
    $width: 100 / ($i + 2);

    .switch-#{$state} {
      label,
      .slide-button {
        width: $width * 1%;
      }
    }

    @for $j from 2 through ($i + 1) {

      .switch-#{$state} input:checked:nth-of-type(#{$j}) ~ .slide-button {
        left: $width * ($j - 1) * 1%;
      }

    }

    .switch-#{$state} input:checked:last-of-type ~ .slide-button {
      left: 100 - $width * 1%;
    }

  }

}

View Raw Code →


The HTML

Here is a sample HTML page you can use to see how these switches work from an interaction and markup standpoint. Note: There is a large chunk of foundation.css at the top of this page so that things render correctly. This isn't best practice, but works for demo purposes here.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />

  <!-- Set the viewport width to device width for mobile -->
  <meta name="viewport" content="width=device-width" />

  <title>Foundation Switch Testing</title>

  <style>
    *,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}html,body{font-size:16px}body{background:white;color:#222222;padding:0;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-weight:normal;font-style:normal;line-height:1;-webkit-font-smoothing:antialiased;}.row{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;}.row [class*="large-"],.row [class*="small-"]{width:100%;float:none;padding-left:0.9375em;padding-right:0.9375em;position:relative;}.row .row{width:auto;max-width:none;min-width:0;margin:0 -0.9375em;}.row .row.collapse{margin:0}.row:before,.row:after{content:" ";display:table;}.row:after{clear:both}@media only screen and (min-width:0.0625em){.row [class*="small-"]{float:left}.row.collapse [class*="small-"]{padding:0}[class*="small-"]:last-child{float:right}[class*="small-"] + [class~="end"]{float:left}.row .small-1{width:8.33333%}.row .small-2{width:16.66667%}.row .small-3{width:25%}.row .small-4{width:33.33333%}.row .small-5{width:41.66667%}.row .small-6{width:50%}.row .small-7{width:58.33333%}.row .small-8{width:66.66667%}.row .small-9{width:75%}.row .small-10{width:83.33333%}.row .small-11{width:91.66667%}.row .small-12{width:100%}}@media only screen and (min-width:48em){.row [class*="large-"]{float:left}.row.collapse [class*="large-"]{padding:0}[class*="large-"]:last-child{float:right}[class*="large-"] + [class~="end"]{float:left}.large-centered{float:none !important;margin:0 auto;}.row .large-1{width:8.33333%}.row .large-2{width:16.66667%}.row .large-3{width:25%}.row .large-4{width:33.33333%}.row .large-5{width:41.66667%}.row .large-6{width:50%}.row .large-7{width:58.33333%}.row .large-8{width:66.66667%}.row .large-9{width:75%}.row .large-10{width:83.33333%}.row .large-11{width:91.66667%}.row .large-12{width:100%}}.panel{background:#f2f2f2;border:solid 1px #e6e6e6;margin:0 0 1.375em 0;padding:1.25em;}.panel >:first-child{margin-top:0}.panel >:last-child{margin-bottom:0}.panel >:first-child{margin-top:0}.panel >:last-child{margin-bottom:0}.panel.callout{background:#2ba6cb;color:#fff;border-color:#2284a1;-webkit-box-shadow:inset 0px 1px 0px rgba(255,255,255,0.5);-moz-box-shadow:inset 0px 1px 0px rgba(255,255,255,0.5);box-shadow:inset 0px 1px 0px rgba(255,255,255,0.5);}.panel.callout a{color:#fff}.panel.callout .button{background:white;border:none;color:#2ba6cb;text-shadow:none;}.panel.callout .button:hover,.panel.callout .button:focus{background:rgba(255,255,255,0.8)}.panel.radius{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.panel.round{-webkit-border-radius:1000px;-moz-border-radius:1000px;border-radius:1000px;}.button{border-style:solid;border-width:1px;cursor:pointer;display:inline-block;font-family:inherit;font-weight:bold;line-height:1;margin:0;position:relative;text-decoration:none;text-align:center;padding-top:0.625em;padding-right:1.25em;padding-bottom:0.6875em;padding-left:1.25em;width:auto;font-size:0.875em;background-color:#2ba6cb;border-color:#2284a1;color:white;-webkit-box-shadow:0 1px 0 rgba(255,255,255,0.5) inset;-moz-box-shadow:0 1px 0 rgba(255,255,255,0.5) inset;box-shadow:0 1px 0 rgba(255,255,255,0.5) inset;}.button:hover,.button:focus{background-color:#2284a1}.button:hover,.button:focus{color:white}.button:active{-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.2) inset;-moz-box-shadow:0 1px 0 rgba(0,0,0,0.2) inset;box-shadow:0 1px 0 rgba(0,0,0,0.2) inset;}.button.large{padding-top:0.9375em;padding-right:1.875em;padding-bottom:1em;padding-left:1.875em;width:auto;font-size:1.0625em;}.button.medium{padding-top:0.625em;padding-right:1.25em;padding-bottom:0.6875em;padding-left:1.25em;width:auto;font-size:0.875em;}.button.small{padding-top:0.4375em;padding-right:0.875em;padding-bottom:0.5em;padding-left:0.875em;width:auto;font-size:0.6875em;}.button.tiny{padding-top:0.3125em;padding-right:0.625em;padding-bottom:0.375em;padding-left:0.625em;width:auto;font-size:0.625em;}.button.expand{padding-top:false;padding-right:0px;padding-bottom:false0.0625em;padding-left:0px;width:100%;}.button.radius{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.button.round{-webkit-border-radius:1000px;-moz-border-radius:1000px;border-radius:1000px;}.button.secondary{background-color:#e9e9e9;border-color:#d0d0d0;color:#333333;}.button.secondary:hover,.button.secondary:focus{background-color:#d0d0d0}.button.secondary:hover,.button.secondary:focus{color:#333333}.button.success{background-color:#5da423;border-color:#457a1a;color:white;}.button.success:hover,.button.success:focus{background-color:#457a1a}.button.success:hover,.button.success:focus{color:white}.button.alert{background-color:#c60f13;border-color:#970b0e;color:white;}.button.alert:hover,.button.alert:focus{background-color:#970b0e}.button.alert:hover,.button.alert:focus{color:white}
  </style>

  <!-- Included CSS Files -->
  <link rel="stylesheet" href="switch.css">

</head>
<body>

  <!-- Test Foundation Components Here -->
  <div class="row">
    <div class="large-12">

      <h3>Switches</h3>
      <h4 class="subheader">Switches can be used instead of regular radio buttons, to switch between two or more options. They are very customizable and use styles from other components such as buttons or panels. </h4>
      <br>

      <div class="row">
        <div class="large-4">
          <a name="sizes"></a>
          <h4>Sizes</h4>
          <p>The switches include the same size styles as buttons, so you can add the additional <code>.tiny</code>, <code>.small</code> or <code>.large</code> classes to change the regular size of the switch. </p>
        </div>
        <br>

        <div class="large-8">

          <div class="row">
            <label>Tiny switch: </label>

            <div class="switch panel tiny">
              <input id="o11" name="switch-tiny" type="radio" checked>
              <label for="o11" onclick="">Option 1</label>

              <input id="o12" name="switch-tiny" type="radio">
              <label for="o12" onclick="">Option 2</label>

              <span class="slide-button button"></span>
            </div>
          </div>

          <div class="row">
            <label>Small switch: </label>

            <div class="switch panel small">
              <input id="o21" name="switch-small" type="radio" checked>
              <label for="o21" onclick="">Option 1</label>

              <input id="o22" name="switch-small" type="radio">
              <label for="o22" onclick="">Option 2</label>

              <span class="slide-button button"></span>
            </div>
          </div>

          <div class="row">
            <label>Regular switch: </label>

            <div class="switch panel">
              <input id="o31" name="switch" type="radio" checked>
              <label for="o31" onclick="">Option 1</label>

              <input id="o32" name="switch" type="radio">
              <label for="o32" onclick="">Option 2</label>

              <span class="slide-button button"></span>
            </div>
          </div>

          <div class="row">
            <label>Large switch: </label>

            <div class="switch panel large">
              <input id="o41" name="switch-large" type="radio" checked>
              <label for="o41" onclick="">Option 1</label>

              <input id="o42" name="switch-large" type="radio">
              <label for="o42" onclick="">Option 2</label>

              <span class="slide-button button"></span>
            </div>
          </div>

        </div>
      </div>

      <div class="row">
        <div class="large-4">
          <a name="styles"></a>
          <h4>Styles</h4>
          <p>Switches use styles from other components, so you can use the classes from buttons, alerts or panels, among others. </p>
        </div>
        <br>

        <div class="large-8">

          <div class="row">
            <label>Secondary switch: </label>

            <div class="switch panel">
              <input id="o51" name="switch-secondary" type="radio" checked>
              <label for="o51" onclick="">Option 1</label>

              <input id="o52" name="switch-secondary" type="radio">
              <label for="o52" onclick="">Option 2</label>

              <span class="slide-button button secondary"></span>
            </div>
          </div>

          <div class="row">
            <label>Success switch: </label>

            <div class="switch panel">
              <input id="o61" name="switch-success" type="radio" checked>
              <label for="o61" onclick="">Option 1</label>

              <input id="o62" name="switch-success" type="radio">
              <label for="o62" onclick="">Option 2</label>

              <span class="slide-button button success"></span>
            </div>
          </div>

          <div class="row">
            <label>Alert switch: </label>

            <div class="switch panel">
              <input id="o71" name="switch-alert" type="radio" checked>
              <label for="o71" onclick="">Option 1</label>

              <input id="o72" name="switch-alert" type="radio">
              <label for="o72" onclick="">Option 2</label>

              <span class="slide-button button alert"></span>
            </div>
          </div>

          <div class="row">
            <label>Radius switch: </label>

            <div class="switch panel">
              <input id="o91" name="switch-radius" type="radio" checked>
              <label for="o91" onclick="">Option 1</label>

              <input id="o92" name="switch-radius" type="radio">
              <label for="o92" onclick="">Option 2</label>

              <span class="slide-button button radius"></span>
            </div>
          </div>

          <div class="row">
            <label>Round switch: </label>

            <div class="switch panel secondary round">
              <input id="o101" name="switch-round" type="radio" checked>
              <label for="o101" onclick="">Option 1</label>

              <input id="o102" name="switch-round" type="radio">
              <label for="o102" onclick="">Option 2</label>

              <span class="slide-button button round"></span>
            </div>
          </div>

        </div>
      </div>

      <div class="row">
        <div class="large-4">
          <a name="multiple-states"></a>
          <h4>Multiple States</h4>
          <p>You can add up to five options in a switch, using the <code>.switch-3</code>, <code>.switch-4</code> or <code>.switch-5</code> classes. If you've got more than five options, it's probably best to use other forms of interaction. </p>
        </div>
        <br>

        <div class="large-8">

          <div class="row">
            <label>Three-state switch: </label>

            <div class="switch-3 panel">
              <input id="o111" name="switch-three" type="radio" checked>
              <label for="o111" onclick="">Option 1</label>

              <input id="o112" name="switch-three" type="radio">
              <label for="o112" onclick="">Option 2</label>

              <input id="o113" name="switch-three" type="radio">
              <label for="o113" onclick="">Option 3</label>

              <span class="slide-button button"></span>
            </div>
          </div>

          <div class="row">
            <label>Four-state switch: </label>

            <div class="switch-4 panel">
              <input id="o121" name="switch-four" type="radio" checked>
              <label for="o121" onclick="">Option 1</label>

              <input id="o122" name="switch-four" type="radio">
              <label for="o122" onclick="">Option 2</label>

              <input id="o123" name="switch-four" type="radio">
              <label for="o123" onclick="">Option 3</label>

              <input id="o124" name="switch-four" type="radio">
              <label for="o124" onclick="">Option 4</label>

              <span class="slide-button button"></span>
            </div>
          </div>

          <div class="row">
            <label>Five-state switch: </label>

            <div class="switch-5 panel">
              <input id="o131" name="switch-five" type="radio" checked>
              <label for="o131" onclick="">Option 1</label>

              <input id="o132" name="switch-five" type="radio">
              <label for="o132" onclick="">Option 2</label>

              <input id="o133" name="switch-five" type="radio">
              <label for="o133" onclick="">Option 3</label>

              <input id="o134" name="switch-five" type="radio">
              <label for="o134" onclick="">Option 4</label>

              <input id="o135" name="switch-five" type="radio">
              <label for="o135" onclick="">Option 5</label>

              <span class="slide-button button"></span>
            </div>
          </div>

        </div>
      </div>

    </div>
  </div>

</body>
</html>

View Raw Code →