Create Accessible Web Forms with Custom Styling

Posted on

  • #accessibility
  • #css
  • #design
  • #forms
  • #html
  • #web

HTML is accessible by default. At least, if you use the right native elements. Very often, developers build custom form controls to meet design needs – neglecting accessibility in the process.

As I'll show in this post, modern browsers enable us to apply custom styling to most native form controls. This way, we can create accessible web forms with a consistent look across all platforms.

Brush and spatula on a brightly painted canvas Photo: © Steve Johnson /

When Design Needs wreck Accessibility

Native form controls like input or select are accessible by default. They receive focus when keyboard users press the tab key. The enter and space key can be used to interact with them, e.g., to mark a checkbox as checked. They communicate their role and current state to screen readers.

Sounds great! You would be mad not to use native form controls and, instead, build custom UI components with non-semantic spaghetti code. Right? Well, the sad truth is: Using custom form controls is a common practice. And most of the time they're not accessible. Let's look at an example for a custom select:

<div class="custom-select"> <div class="select-trigger"> <span>PlayStation 5</span> <div class="arrow"></div> </div> <div class="custom-options"> <span class="option selected"> PlayStation 5 </span> <span class="option">Nintendo Switch</span> <span class="option">Xbox Series X</span> </div> </div>

By itself, this mess of div and span tags is not accessible to keyboard and screen reader users. Yes, you can fix this with ARIA attributes and keydown event listeners. But many developers are either not aware of this or simply don't care. What they care about is styling the custom elements to meet the design needs of their clients.

Why Styling Native Form Controls can be challenging

Using CSS to style form controls has historically been difficult. Browsers apply their own default styling and form controls vary greatly in how easy they are to customize with CSS. Fortunately, this is getting easier as old browsers are retired (death to IE11) and modern browsers offer more features to use.

Mozilla has a great article on Styling web forms, which I highly recommend. They categorize native form controls into three groups: “The good”, “The bad”, and “The ugly”. I mostly agree with them, except for considering checkboxes and radio buttons difficult to style. This may have been true in the past, but modern CSS allows you to do whatever you want as I'll show you.

Styling Example 1: Radio Buttons

In order to style your radio buttons with CSS, you need to set the value of the appearance property to none. This removes the default appearance applied by the browser and operating system. Now you can get creative and even apply smooth CSS transitions if you like:

input[type="radio"] { -webkit-appearance: none; appearance: none; box-sizing: border-box; position: relative; width: 1.4em; height: 1.4em; border: 0.15rem solid darkred; border-radius: 50%; } input[type="radio"]::before { position: absolute; top: 50%; left: 50%; content: ""; width: 0.7em; height: 0.7em; border-radius: 50%; background-color: darkred; /* scale(0) hides the dot in unchecked state */ transform: translate(-50%, -50%) scale(0); transform-origin: center; transition: all 0.3s ease-in; } input[type="radio"]:checked::before { transform: translate(-50%, -50%) scale(1); transition: all 0.3s cubic-bezier(0.25, 0.25, 0.56, 2); }

In my example, I defined the unchecked state of the radio button to appear as a darkred circle. With the help of the ::before pseudo-element and the :checked pseudo-class, a darkred dot appears inside the circle when the radio button is selected. Check out the demo on Codepen.

Styling Example 2: Select

The select element belongs to the category “The ugly”. The problem with the form controls in this group is that they have very different default looks across browsers. You can style them to some degree with CSS, but some parts of their internals are literally impossible to style.

You can use the appearance property to override the default look of the select element. But this will not affect the list of options that appears when you click on the element. Styling the option element with CSS is highly limited and only supported by some browsers.

In my opinion, the styling options are sufficient to meet basic design needs (e.g. color and font). Take a look at my demo on Codepen where I used the following styling:

select { -webkit-appearance: none; appearance: none; box-sizing: border-box; background-color: white; background-image: url('/assets/custom_arrow_for_select.svg'); background-position: right; background-repeat: no-repeat; background-size: contain; border: 0.25rem solid darkred; padding: 0.25em; /* Leave enough space for the arrow on the right */ padding-right: 2em; }

Native HTML is always better

What I wanted to show with this post is: Using native HTML is always better than messing around with custom UI components. Because keyboard and screen reader accessibility is already built in. Therefore, I would always pick the native form control if it can be sufficiently styled with CSS.

Posted on