Cloud Four Blog

Technical notes, War stories and anecdotes

Two pretty-good techniques for styling tricky form elements

Confession time: For most of my career, I despised form elements. Checkboxes, radios, selects and file inputs seemed to gleefully defy what little control I expected from an HTML element. Their penchant for idiosyncracy drove me to almost as much hair-pulling and teeth-gnashing as IE6 or web-safe fonts.

These days, my frustration with form elements has quieted. Partly that’s because browsers and development tools are so much better. But more significantly, I now understand the benefits of surrendering some control to the operating system. As devices continue to accept a greater and greater variety of input methods (keyboard, mouse, touch, voice, gesture, remote, etc.) while browsers adopt an astounding variety of new input types , it’s a gift for vendors to provide default experiences consistent with the user’s expectations of the platform.

So I no longer strive for “pixel perfection” when styling form elements. I don’t need absolute control. All I want is something easy to tap that feels intentional.

When the browser defaults don’t get me there, here are my go-to workarounds.

Checkboxes and Radios: Styled Sibling

This technique works in any browser that supports CSS3 selectors (basically IE9+). If you read Radio-Controlled Web Design a few weeks ago, this should feel familiar. Let’s start with a checkbox example.

We’ll need a few HTML elements:

  • The <input> itself.
  • A dummy element to style (right next to the <input>).
  • A containing <label> that passes click events to the aforementioned <input>.

I like to wrap the <input> and dummy elements in a container to keep everything nice and tidy, but strictly speaking it isn’t required. Here’s what that markup might look like:

<label>
  <span class="checkbox">
    <input type="checkbox">
    <span class="checkbox-value" aria-hidden="true"></span>
  </span>
  Set phasers to stun
</label>

We’re now free to visually hide the checkbox, styling .checkbox-value however we like:

/* hide the "real" checkbox visually */
.checkbox input {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}
 
/* style the "fake" checkbox */
.checkbox-value {
  /* default/unchecked styles */
}
input:checked + .checkbox-value {
  /* checked styles */
}

When the user clicks the label, the click is passed along to the <input>, which toggles the state of :checked, which affects the appearance of .checkbox-value.

Here’s an example that styles the checkbox like an iOS-style switch:

See the Pen Styled checkbox by Tyler Sticka (@tylersticka) on CodePen.

Here’s the same idea applied to radio buttons with a slightly more conventional design (incorporating a base64-encoded SVG checkmark):

See the Pen Styled radios by Tyler Sticka (@tylersticka) on CodePen.

This technique has a few drawbacks. It requires some extra markup. It won’t work in IE8 or earlier without a fallback. It could probably use another pass for accessibility. But compared to most of the JavaScript solutions I’ve tried, this feels straightforward, consistent and predictable.

Selects and File Inputs: Transparent Overlay + JavaScript

For more complex elements like <select> and <input type="file">, we can’t get by on CSS alone (though it gets us further than one might expect).

Our markup is similar to the previous set of checkbox/radio examples, except we won’t need a <label> for click events:

<div class="select">
  <select>
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
  </select>
  <span class="select-value" aria-hidden="true"></span>
</div>

Instead of hiding the <select> entirely, we want to position it over the rest of our element, allowing it to intercept click events and correctly position any dropdown it may display. Because this technique relies on JavaScript, we’ll qualify some of our selectors with .js (since you’re probably already using Modernizr).

.js .select {
  position: relative;
  /* default styles */
}
.js .select:hover {
  /* hover styles */
}
.js .select.focus {
  /* focus styles */
}
 
/* nicer default styles for "real" <select> */
.select select {
  cursor: pointer;
  display: block;
  width: 100%;
}
/* hide and overlay when JavaScript is enabled */
.js .select select {
  left: 0;
  height: 100%;
  min-height: 100%;
  min-width: 100%;
  opacity: 0;
  position: absolute;
  top: 0;
}

Already, this “works.” Options will display on click. But there are some problems. The value doesn’t update. There are no hover or focus styles. That’s where JavaScript comes in!

(Although I’ve chosen to write this in jQuery for the sake of readability, remember: You Might Not Need jQuery!)

// For each .select element
$('.select').each(function(){
  // Save some elements as variables
  var $element = $(this);
  var $select = $element.find('select');
  var $value = $element.find('.select-value');
  // Bind event handlers to <select>
  $select.on({
    // On change or keyup, update the value text
    'change keyup': function () {
      $value.text($select.val());
    },
    // On focus, add the focus class
    'focus': function () {
      $element.addClass('focus');
    },
    // On blur, remove the focus class
    'blur': function () {
      $element.removeClass('focus');
    }
  });
  // Trigger the change event so the value
  // is current
  $select.trigger('change');
});

Here’s how all of that comes together:

See the Pen Styled select by Tyler Sticka (@tylersticka) on CodePen.

With some tweaks, the same basic technique can also work for file inputs (assuming experimental WebKit/Blink features aren’t your thing):

See the Pen Styled file input by Tyler Sticka (@tylersticka) on CodePen.

This idea isn’t new. Peter-Paul Koch wrote about it quite a while back. Yet I rarely see it in use outside of a few large mobile frameworks. I’m honestly not sure why.

…and beyond?

What do all of these examples have in common? They don’t mess with the form element too much! By worrying less about customizing behavior and more on simply triggering it, we can indulge some of our designerly impulses without discarding all a given platform has to offer.

Consistency and functionality… no hair-pulling or teeth-gnashing required!

Update: September 8, 2014

A reader pointed out that the select example wasn’t responding to keyboard input in Firefox. I discovered that Firefox doesn’t fire the change event for selects like other browsers do, so I’ve updated the demo and example code so that it binds to both change and keyup.

I also learned that Firefox doesn’t show the full dropdown on any keypress, but this seems to be true of unstyled <select> elements as well. I encourage developers to use these examples as a starting point, and to augment usability shortcomings on a case-by-case basis if the default browser behavior isn’t cutting it.

Fixed and inflexible

Even before smartphones came along and dashed any hope for a 960-pixel-wide web, designers and organizations have struggled with the challenge of prioritizing and composing content that scrolls. Our screens act like windows to content of variable size and scale, demanding an awful lot of abstract thinking to design for. Sometimes we’re successful, revising content, designing modern day deliverables and embracing compromise like we know in our hearts we should. Other times, we convince ourselves that we can predict this inherently unpredictable medium, making decisions that age quickly and poorly by prioritizing the window instead of the content.

Most recently, I’ve noticed a sharp uptick in the number of requests I receive to explore fixed-position (aka “sticky”) interface elements. Fixed elements are positioned relative to the viewport instead of the page, allowing them to maintain position even as the document scrolls. Some of the most popular sites on the web employ “sticky” menus, and with good reason… when applied thoughtfully, they can yield substantial usability improvements.

But when fixed positioning is used without care, restraint or precision, it can have disastrous consequences. Here are some of the reasons why.

We can’t predict how much space we have.

Our industry has a nasty habit of quietly embracing display resolution “standards” that are mostly fantasy… design decisions are a lot easier if you assume all devices are 320 pixels wide by 480 pixels tall (and users never turn them sideways). The inconvenient truth is that display resolutions vary wildly. Each additional “sticky” element increases the risk of obscuring the page content mostly (or entirely) for some users… in which case the page might as well not exist at all.

We can’t get those pixels back.

Fixed elements aren’t just “prominent,” they photobomb the interface, robbing focus and attention from what really matters — the content! Before you make something “sticky,” consider reevaluating the element’s importance relative to the entire page (instead of on its own merits).

It makes scrolling tougher.

As evidenced by the years-long debate over “The Fold,” the fear that users won’t know (or lack willingness) to scroll persists to this day. When that fear is real, then fixed positioning is a godsend, ensuring the visibility of content regardless of scroll position.

But those fears don’t mesh with reality very well.

Today’s users are so familiar with scrolling that most mobile browsers will hide the scrollbar entirely. “Sticky” elements complicate matters by reducing or obscuring the scrollable area, forcing the user to swipe more carefully to avoid accidental actions.

Ironically, our desire to alleviate the supposed “difficulty” of scrolling may make scrolling that much more difficult!

It can slow everything down.

Users care about speed. But speed isn’t all about navigation… there’s also the overall speed of the experience. Fixed positioning can result in strange browser-specific quirks or even costly repaints, potentially counteracting any efficiency you might have gained.

It may not actually work.

Mobile websites share a lot of their design vocabulary with native mobile apps, where fixed headers, menus and tab bars are commonplace. This makes it easy to forget that fixed positioning as we know it is relatively new to the web, and often unreliable.

“Sticky” headers, footers and navigation flourished in the 1990s, often implemented using frames. When frames fell out of fashion in the early 2000s, most browsers did not support fixed positioning using CSS. In the absence of frames, position: fixed or consistent, intuitive JavaScript, fixed positioning became just another discarded Web 1.0 trend… at least until CSS support arrived in IE7.

But smartphone browsers have historically not supported position: fixed as predictably as their desktop counterparts. It was entirely absent from mobile Safari prior to iOS5, and largely unusable in Android browsers prior to Honeycomb. To this day, behavior can be inconsistent across platforms. To quote Brad Frost in his excellent post on Fixed Positioning in Mobile Browsers, “‘support’ isn’t exactly binary.”

Since you can’t rely on support for fixed positioning, you’ll need to make sure your experience works without it anyway. Which begs the question…

Why position: fixed at all?

I believe there are plenty of interface elements that benefit from fixed positioning, provided they follow a few best practices:

  • The “sticky” element is clearly more important than everything else on the page.
  • The footprint of the element is modest enough that it does not obscure too much of the page content (even in landscape).
  • Any efficiency gained from the element’s consistent availability is significantly greater than any lost as a result of the element’s inclusion (due to performance, obscuring of page content, etc.).
  • There should only be one “global” (navigation, tab bar, etc.) and one “temporary” (modal, dialog, etc.) fixed-position element on-screen at any one time.
  • Fixed positioning should always be an enhancement. Your interfaces should never rely on it.

If any of these considerations completely upend your design aspirations, you may want to rethink your user experience with less rigidity. As John Allsopp so aptly put it fourteen years ago, “The journey begins by letting go of control, and becoming flexible.”

Slow and ugly

Many years ago at a previous job, I was responsible for pushing our organization to adopt a new ticket tracking system. We decided to use an open source project called Request Tracker (RT).

This was before the days of Zen Desk and similar services. RT matched our requirements best. I worked with our sysadmins and engineers to get the software running and rolled it out to the organization.

It was a failure. People were openly swearing at RT, and I’m sure they were privately cursing me for forcing them to use it.

While I sympathized with the complaints, I felt hamstrung because RT was deployed on an underpowered server and we had not been allowed to modify the look and feel. Back then RT was a bit of a dog both from a performance and aesthetics perspective.

As I worked with engineering to secure better hardware and get someone to assist with the design, the number of grievances continued to pile up.

One conversation stands out to me. A member of the management team told me that the fact that customers couldn’t reset their password in RT was unacceptable and as long as that was true, we couldn’t use RT.

I listened to the objections and kept a list of the issues that were raised, but I kept my focus on making RT faster and more attractive.

A few months later we relaunched RT. It was met with skepticism, but to the credit of my colleagues, they gave me and it a second chance.

It wasn’t too long after the relaunch that previous skeptics were swearing by the system. One of the team members became the master of RT and made a bunch of process improvements that I would have never considered.

What happened to that list of complaints that I gathered? We didn’t address a single one of them. Passwords worked exactly as they did before.

We simply made RT fast and attractive.

I have a simple rule when it comes to user experience: when something is slow and ugly, nothing else matters.

Until you address those two issues, you’re not going to be tell whether other complaints are real or red herrings.

Fantastic intro to new srcset and sizes

I normally don’t write a post simply to link to another article, but if you’ve enjoyed the things I’ve written about responsive images, you really should read what Eric Portis wrote about Srcset and Sizes.

Eric describes the logic behind srcset and sizes, how they are part of the new picture proposed standard, and how to use them.

Easy. Peas.

What’s New in hideShowPassword 2

Last June, we released our hideShowPassword plugin for improving the experience of password-entry (particularly on mobile devices). As of this writing, it’s our most-starred and most-forked project on GitHub.

We’ve released a handful of incremental and bug-fix updates since then, but today we’re shipping a full-fledged Version 2 with several noteworthy improvements.

Simpler Usage

The plugin’s “inner toggle” feature tended to steal the show, yet its syntax was verbose:

$(selector).hideShowPassword(true, { innerToggle: true });

We now support a simpler, shorthand syntax for enabling the inner toggle:

$(selector).hideShowPassword(true, true);
 
// enable toggle but hide till input focus:
$(selector).hideShowPassword(true, 'focus');
 
// using shorthand method:
$(selector).showPassword('focus');

Improved Accessibility

My personal favorite feature is the improved accessibility of the inner toggle button. Some changes we made based on real-world feedback:

  • The toggle button element is now a <button> by default (it was previously a <div>).

  • Several accessibility attributes have been added to better communicate the purpose and state of the button to assistive devices.

  • The toggle should now be keyboard-accessible, with space and enter key events attached where necessary.

We understand every project’s different, and you may need to tweak some of these new properties. Luckily, Version 2 also includes…

Overhauled Options

Just about anything we could make an option, we have. There are so many new options, we had to re-organize the options object to accommodate them all. See the full list here.

Built For jQuery and Zepto

Version 1 of hideShowPassword supported Zepto, but we’ve decided to simplify the plugin’s dependencies by only supporting jQuery from here on out. This makes sense to us for a few reasons:

  • Zepto’s data module was required, necessitating a custom build of Zepto to work at all.

  • The plugin’s inner toggle features could be unpredictable given Zepto’s simpler width and height calculation methods.

  • Zepto does not support AMD, and likely never will. We introduced AMD support to hideShowPassword in our first minor update based on developer feedback.

  • While Zepto’s modest file size remains an attractive feature, its performance once loaded may not be so competitive.

Get It Now!

All the aforementioned improvements (plus more fixes and tweaks) are available right now on GitHub (which is also a great place to report issues or request features). Check out the live demo if you remain unconvinced.

If you use the plugin in a project, please drop us a line and let us know. We’d love to hear about it!