HTML & CSS
Article

Theming Form Elements with Sass

By Dennis Gaebel

Form inputs without a doubt encompass a sizable portion of the Web. Since form controls can and will be encountered by users daily, it only seems fitting to lend our attention to a few suspects by furnishing each one with Sass’ strength to help us theme our project’s input’s swiftly.

Placeholders

A placeholder is a hint to users as to what information can be entered within the corresponding control. It applies when the value of the type attribute is set as text, search, tel, url or email; otherwise it’s ignored. Unfortunately to style the placeholder requires the appropriate vendor prefixes associated with each pseudo required so authors can deliver coverage across the major browser vendors such as IE, Firefox, Safari, Chrome and Opera.

A Mixin Helper

This placeholder @mixin can be used within a variety of contexts such as applying it alone or combined with a selector of your choosing. I’ve also taken the liberty of constructing a Sass map that contains all the properties you’re allowed to style.

index.html

<label for="username">Name</label>
<input type="text" name="username" id="username" placeholder="first, last">

_placeholder-mixin.scss

The @at-root directive used by these mixins works by switching context where the nested call in your Sass is placed and pulls the declaration to the top level of the selector chain.

Note: At the time of this post node-sass is unable to compile this placeholder mixin due to interpolation issues with the line @at-root #{&}#{$pseudo}.

$pseudo-phprefix: "::-webkit-input-placeholder" "::-moz-placeholder" ":-ms-input-placeholder" "::placeholder";

$ph-styles: (
  font-family: sans-serif,
  font-size: medium,
  font-style: normal,
  font-weight: normal,
  color: inherit,
  letter-spacing : normal,
  line-height: normal,
  text-align: inherit,
  text-decoration: inherit,
  padding: 0
);

@mixin placeholder-theme($styles) {
  @each $pseudo in $pseudo-phprefix {

    @at-root #{&}#{$pseudo} {
      @each $key, $value in $styles {
        #{$key}: #{$value};
      }
    }

  }
}

@mixin placeholder {
  @each $pseudo in $pseudo-phprefix {

    @at-root #{&}#{$pseudo} {
      @content
    }

  }
}

With the required @mixin defined we can make it’s call as a standalone statement that will universally style placeholders with the properties and values you pass. The second method takes advantage of using a targeted selector to avoid globally styling certain and unwanted placeholders in your system.

_placeholder-mixin.scss

@include placeholder { color: red; }

input {
  @include placeholder-theme($ph-styles);
}

CSS Output

::-webkit-input-placeholder {
  color: red;
}
::-moz-placeholder {
  color: red;
}
:-ms-input-placeholder {
  color: red;
}
::placeholder {
  color: red;
}
input::-webkit-input-placeholder {
  font-family: sans-serif;
  font-size: medium;
  font-style: normal;
  font-weight: normal;
  color: inherit;
  letter-spacing: normal;
  line-height: normal;
  text-align: inherit;
  text-decoration: inherit;
  padding: 0;
}
input::-moz-placeholder {
  font-family: sans-serif;
  font-size: medium;
  font-style: normal;
  font-weight: normal;
  color: inherit;
  letter-spacing: normal;
  line-height: normal;
  text-align: inherit;
  text-decoration: inherit;
  padding: 0;
}
input:-ms-input-placeholder {
  font-family: sans-serif;
  font-size: medium;
  font-style: normal;
  font-weight: normal;
  color: inherit;
  letter-spacing: normal;
  line-height: normal;
  text-align: inherit;
  text-decoration: inherit;
  padding: 0;
}
input::placeholder {
  font-family: sans-serif;
  font-size: medium;
  font-style: normal;
  font-weight: normal;
  color: inherit;
  letter-spacing: normal;
  line-height: normal;
  text-align: inherit;
  text-decoration: inherit;
  padding: 0;
}

Words of Caution: Avoid using the placeholder attribute in place of a label. Both vary in purpose as the label attribute describes the role of the form element; that is, it indicates what kind of information is expected. The placeholder attribute is a hint about the format the content should take. There are cases in which the placeholder attribute is never displayed to the user, so the form must be understandable without it.

Jumping Placeholder Effect

This is a pretty cool effect to style and theme, but only reacts in WebKit/Blink. Some also refer to this as the “Floating Label” pattern when upon the time of it’s debut caused fascinating discussions regarding the need for such a pattern.

See the Pen Jumping Placeholder by SitePoint (@SitePoint) on CodePen.

index.html

<label for="phone">Cell Phone</label>
<input type="tel" name="phone" class="jpinput" id="phone" placeholder="(555) 555-5555">

The magic happens due to the pseudo selector ::-webkit-input-placeholder[style*=hidden] that allows authors to style the placeholder’s state once a user begins to enter his/her information. I would love to find a Mozilla equivalent to the aforementioned selector, but I’m coming up empty handed in my searches. David Bushell has a great demo I’ve linked to below that takes advantage of pseudo validity selectors, but of course the method does have a few cons.

_jp-input.scss

$jpinput-height: 40px;
$jpinput-radius: 4px;
$jpinput-padding: 10px 16px;
$jpinput-bg: #8DAA91;
$jpinput-color: #4F4137;
$jpinput-ph-color: $jpinput-color;
$jpinput-phide-color: $jpinput-bg;

input {
  appearance: none;
  box-sizing: border-box;
  border-radius: $jpinput-radius;
  display: inline-block;
  outline: 0;
  width: 100%;
}

.jpinput {
  height: $jpinput-height;
  padding: $jpinput-padding;
  transition: transform 225ms ease-in-out;
  background: $jpinput-bg;
  color: $jpinput-color;

  @include placeholder {
    position: relative;
    top: 0;
    left: 0;
    transition: all 300ms ease-in-out;
    color: rgba($jpinput-ph-color, .5);
  }
}

.jpinput::-webkit-input-placeholder[style*=hidden] {
  transform: translateY(-$jpinput-height);
  opacity: 1;
  visibility: visible !important;
  color: $jpinput-phide-color;
}

There are certainly other approaches that I invite you to dive into further in order to identify the pros and cons. Just let it be known that many of the options that exist at the time of this writing are usually succumbing to JavaScript for support.

Labels

The HTML label represents a caption for an item within the interface and can be associated with it’s correlating control. It also helps to indicate what kind of information is to be expected.

Secure Input

We can use the label to act as a sliding door in order to hide entered information when the input has it’s focus removed. It’s a slick way to conceal text that is intended to be private such as a credit card number or social security number. This example also takes advantage of the Sass placeholder @mixin I previously shared. The label may be positioned before, after or around the associated control in case you’re curious about this pattern.

See the Pen Secure Input by SitePoint (@SitePoint) on CodePen.

index.html

<div class="slabel">
  <input id="credit-card" type="text" pattern="[0-9]{13,16}" placeholder="xxxx-xxxx-xxxx-xxxx">
  <label for="credit-card">Credit Card</label>
</div>

_slabel-input.scss

$slabel-theme: (
  border: 0,
  radius: 0,
  padding: 0,
  font: inherit,
  bg: white,
  label-bg: white,
  label-color: inherit,
  error-color: #E82C0C,
  placeholder-color: #B9E0D6,
  success-color: #5C832F
);

Checkout these awesome patterns for labels by Jordano Aragão on CodePen that have quite a bit of personality and delight.

Inputs

The input element is used to create interactive controls with forms and accept data from a user’s input. Inputs usually come in the form of text, email, number, radios, checkboxes and even buttons.

Select

Here’s a clean way to style a select input without using a workaround or dirty technique to change the appearance. In most solutions that are leveraging JavaScript the native select element is set to display: none and churned into a set of divs. Clients love it, but authors despise it. I think the approach that follows is a win for everyone until a more native solution with CSS can be achieved. It allows authors to style the browsers that behave and show a solid fallback for those that don’t.

See the Pen Select Choice by SitePoint (@SitePoint) on CodePen.

index.html

<select>
  <option>Property Closing*</option>
  <option>open</option>
  <option>closed</option>
</select>

_select-input.scss

$select-arrow: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4IiB3aWR0aD0iNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE0LjgzIDE2LjQybDkuMTcgOS4xNyA5LjE3LTkuMTcgMi44MyAyLjgzLTEyIDEyLTEyLTEyeiIvPjxwYXRoIGQ9Ik0wLS43NWg0OHY0OGgtNDh6IiBmaWxsPSJub25lIi8+PC9zdmc+';
$select-padding: 0;

select {
  border-radius: 0;
  -webkit-appearance: none; // autoprefixer won't add this and we still need it
  appearance: none;
  cursor: pointer;
  padding: $select-padding;
  width: 100%;

  @media screen and (-webkit-min-device-pixel-ratio: 0) {
    background-image: url(#{$select-arrow});
    background-position: right 0 top 50%;
    background-repeat: no-repeat;
  }
}

Read the original article by Lea Verou discussing this approach or checkout these amazing Select Inspirations by Tympanus.

Radio

A radio represents a selection of one item from a list of items. This input is usually used in situations where a user has a single choice he/she can choose from whereas a checkbox scenario allows multiple choices to be selected.

This custom radio that follows is a Sass based approach and refinement to the original version posted by Mark Otto on WTF, forms? that also includes some updated positioning techniques using CSS transforms for the inner circle plus it scales really well and super simplistic to get started customizing.

See the Pen Radio Control by SitePoint (@SitePoint) on CodePen.

index.html

<label for="radioa" class="input-control radio">
  <input type="radio" id="radioa" name="radio" value="radio-value">
  <span class="input-control__indicator"></span> Send Me Stuff!
</label>

<label for="radiob" class="input-control radio">
  <input type="radio" id="radiob" name="radio" value="radio-value">
  <span class="input-control__indicator"></span> Don't send a friggin' thing.
</label>

_input-radio.scss

$input-radius: 0 !default;
$input-unit: 16px !default; // accepts px, em, rem
$input-spacing: $input-unit * 1.5 !default; // adjust depending on font-family
$input-font: sans-serif !default;

$radio-bg: white !default;
$radio-txt-color: #AACCFF !default;

$radio-checked-custom: (
  background: #0081D0
) !default;
$radio-checked: #222233 !default;
$radio-checked-focus: white !default;

Take a moment to gander at these awesome Animated Radio Inputs by Samurai for further exploration of ideas and inspiration.

Checkbox

The input element with a type attribute value of “checkbox” represents a state or option that can be toggled.

SVG Checkbox

This approach to follow uses an SVG technique originally demonstrated by Sara Soueidan to create the custom checkbox and the x shape using the element. This element uses the stroke-dasharray and stroke-dashoffset SVG properties available to us authors in CSS to animate the selection (the X). This example builds upon the original demo I mentioned and adds the :focus state along with a few other goodies to help size and customize this input to fit your needs.

See the Pen SVG Checkbox by SitePoint (@SitePoint) on CodePen.

index.html

<input type="checkbox" id="optiona" name="optiona">
<label for="optiona">Click Me
  <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <path d="M 10 10 L 90 90"></path>
    <path d="M 90 10 L 10 90"></path>
  </svg>
</label>

_checkbox-input.scss

$font-size: 2em; // adjust accordingly
$ratio: 1; // adjust accordingly
$size: $ratio + em;
$ratio: $ratio;
$gutter: 5px; // adjust accordingly
$stroke-dash: 270;
$stroke: (
  thickness: $gutter/3,
  style: solid,
  color: #fff
);
$mark-ischecked: (
  stroke-dashoffset: 0
); // define css properties
$label-ischecked: (); // define css properties
$svg-ischecked: (); // define css properties

Slider Checkbox

Here’s a checkbox I use on the Transformicons project that resembles a slider. It’s primarily built with Sass, but it’s very reusable and possesses minimal code to customize and style. Take note that this custom input pattern also allows accessibility in terms of keyboard navigation. Once the user places focus on the input via the tab key a color indication will notify the user. Once this notification takes place the user can take action with their spacebar to set the choice.

See the Pen Transformicons Checkbox by SitePoint (@SitePoint) on CodePen.

index.html

<div class="slider-checkbox">
  <input type="checkbox" id="option" aria-checked="false" role="checkbox">
  <label for="option">Label Text</label>
</div>

The setup provided through the Sass configuration lets authors decide on the style of the input by accepting the values ’rounded’ or ‘null’. The ‘null’ value will turn off the border-radius for the thumbslider as well as the track it sits within. This will essentially just make the input boxy.

_slider-checkbox.scss

$checkbox-style: rounded; // accepts 'rounded' or 'null'
$checkbox-sizing-unit: 6rem; // adjust this value for sizing
$checkbox-height: $checkbox-sizing-unit / 1/4;
$checkbox-position-unit: $checkbox-sizing-unit * 3/4;
$checkbox-speed: 150ms;
$checkbox-off-bg: #E8DFE7;
$checkbox-on-bg: #4fbe79;
$checkbox-btn-bg: #FFFFFD;

Validity States

Validity pseudo-selectors are used to style interactive elements based on an evaluation of the user’s input such as :invalid and :valid. Here’s a Sass @mixin to help theme out those particular cases where you want to be all fancy like. A nice little note to be aware of is that :invalid styles apply when the input has the required attribute (even if the input is empty).

@mixin valid {
  &:valid,
  &:empty:valid,
  &:focus:empty:valid {
    @content;
  }
}

@mixin invalid {
  &:invalid,
  &:focus:invalid {
    @content;
  }
}

Here’s a few more psuedos to make your head hurt in terms of forgotten states when it comes to inputs and their control states.

:required
:optional
:enabled
:disabled
:not(:checked)
:in-range
:out-of-range
:user-error
:empty
:blank

Conclusion

If you’d like to go further into the land of styling native form elements here’s a whole bunch of cross browser specific pseudo-elements to knock your socks off trying. If you’d like to learn more about validity states you can set aside some time and read the W3C specification at your leisure. Happy form styling everyone!

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Front-end, once a week, for free.