Linked radio buttons with CSS

Screenshot of styled radio buttons
Regular form elements, heavily styled.


It’s a pattern we see fairly often across the web: a series of ‘buttons’ connected by lines to indicate procession, ratings or steps. The trick is to make it as flexible and accessible as possible, and this is perfectly achievable without JavaScript.

Using regular form elements as our base, we can use a little extra markup and some trickery with CSS ::before and ::after to create attractive (or… excessively pink) results without sacrificing the underlying accessibility.


So we start with a regular ordered list:

<ol class="nodes">
 <li class="node">
   <input class="radio" type="radio" id="r1" name="radio-set">
   <label class="label" for="r1">1</label>
 <li> ... </li>

We’re using accessible markup, making sure we have a label and that the label is referencing the field (via for=). Any interaction with the label will be passed onto the field itself, which is key as we’re going to play with the label to give us our fancy inputs.


We take the label, and use ::before and ::after to position circles on top of the real form input to mask it, and getting our desired look. As these pseudo-elements are children of the <label>, any interaction with this label is passed through to the form field below. We simply layer things up:




.label {

 &::before {
  display: block;
  position: absolute;
  left: 0; right: 0; bottom: 0; top: 0; // trick to fill element
  margin: auto;
  z-index: 1; // ensures this is displayed above form field
  width: 60px;
  height: 60px;
  content: " ";
  border-radius: 50%;
  background-color: pink;
 &::after {

The final step is ensuring that the :checked state of the radio button is reflected by our fancy styling:

 .radio:checked + .label::after {
   background-color: white;

As we want our list to adapt to the space available in the browser, we can use css flex with justification property to ensure each li is evenly spaced:

.node {
 display: flex;
 justify-content: space-between;

By adding an additional  ::after on the .node itself, we can join the nodes together. Note, we’re deliberately offsetting the start of the pseudo-element so that the line stretches from the centre of the element to the right edge of the <li>.

.node::after {
  display: block;
  position: absolute;
  left: 60%; right: 0; bottom: 0; top: 50%;
  content: " ";
  height: 6px; width: 100%;
  background-color: lighten($color-base, 20%);

Finally, you’ll need to remove the line from the last child as we do not want a line going off into nowhere:

.node:last-child {
  &::after {
   display: none;

Interaction and animation

By adding transforms and transition delays we can achieve all kinds of over-the-top effects. Advice: don’t over use this, remember the UX.

Go have a play (CodePen)

Dev notes

We’re making heavy use of display:flex and positioned elements here, and while it all remains flexible across most modern browsers, flex can be rather buggy. You could probably achieve much of this using CSS display:table, or if you were daring, regular float.

Visible password toggle with CSS and JS

Password fields usually mask their content, but is that really good UX?

UX challenges of password fields

  • User ‘loses their place’ forcing them to start entering the password again.
  • Password hard to type or complex, no visual guide to text being entered.
  • Difficulty in paste a password from elsewhere.
  • Difficult to visually check validity of password before submitting.
  • Hard to copy password elsewhere before submitting.

Technical solution

An increasingly common pattern is to allow the user to toggle the visibility of the password field content, which has a number of accessibility benefits.

Screenshot of form with regular password field
Regular password field.

Following progressive enhancement we can do this simply in JavaScript, however there are a few things to consider:


  1. Many users have password manager add-ons—such as LastPass—which may adapt the display of password fields, so we have to be careful when it comes to styling the field itself.
  2. Changing the field <input type="password" ...> to text in JavaScript is simple enough, but it may confuse password managers (even those built into a browser) and irritate users (because their password manager is now broken…).
  3. There’s a possibility that the browser could cache or save the state of the field in plain text, which would be a Bad Thing.
Screenshot of password field with its content being displayed.
Password field with content revealed.

Taking point 2, we can be a little smarter about how we handle form submits and ensure we only ever submit password fields, regardless of a toggled display state. This prevents point 3.

By placing the toggle outside of the password field, we can avoid most visual clashes with add-ons or password managers, covering point 1.

The are downsides to this solution: The user has to have JavaScript enable for start, which you cannot guarantee (but thanks to progressive enhancement, the form will still work). Secondly, it’s still possible for password managers to do strange things to the form fields which cannot be accounted for.

A CodePen demonstrating this technique can be found here.

Custom checkboxes with CSS

Along similar lines to this post on radio controls in forms, we can simply style checkboxes by using :after or :before pseudo-elements on the controls label.


 <input type="checkbox" class="form__checkbox" id="checkbox1" />
 <label for="checkbox1" class="my-label">Checkbox 1</label>


 &:checked + .my-label:before {
   // the on styling for the box, using a url for a tick
   content: url( ... );

.my-label {
 &:before {
  // The 'unchecked' styling for the box
Screenshot of unchecked checkbox demo
Unchecked checkboxes
Screenshot of checked checkbox demo
Checked checkbox

See it in action with this CodePen demo.

Custom radio buttons with CSS

Using a combination of :after and :before selectors on the controls <label> to position pseudo-elements over the real form control.

This selector lets us use the :checked state of the element to apply a style to the controls label:

  // SCSS
  .checkbox:checked + .checkbox-label:after {
    // ... adds a 'spot' to indicate checked status

We then also need to make sure we handle focus on the control so that our fancy styling can also indicate state on keyboard navigation:

  // SCSS
  .checkbox:focus + .checkbox-label:before{
    // ... add some styling to indicate focus/keyboard nav

Here’s a demo of solution on CodePen

Customise radio buttons with only CSS

Dev notes

I’m not a huge fan of heavily customised form elements due to the huge variety of implementations of form controls on different devices. For example, how <select> elements are handled on mobile vs. desktop. Avoid the pain of trying to fix countless display bugs and keep your form customisation as simple as possible.