AGS Logo AGS Logo

Styling Radio Buttons
A CSS Solution

Radio inputs are one of those input types that are notoriously difficult to style because we don't have a whole lot of control over the style of the native input. Let's explore 2 different pure CSS options we can take.

Accent Color Property

The first option, which only recently became available to us is to set a color value to the accent-color property [1] for the input. Now added to the latest versions of the major browsers (Chrome, Edge, Firefox and Safari),[2] the accent-color takes a color and applies it to the control. Figure 1 shows 2 sets of radio buttons, the first has an accent-property value of auto, making the selected radio blue, the default color for my system. The second set has an accent-color value of #96055b so the selected radio is burgundy.

accent-color applied to radio buttons

Using accent-color is a great way to make the control match the rest of our application quickly, but if we want to change anything other than the color, we need to replace the native display with our own.

Custom Radio

The first thing we need to do in order to replace the native control display with our own, is hide the default display using the appearance property and reset the styles as seen in Listing 1.

input[type="radio"] {
  /* hide the native control */
  appearance: none;
  margin: 0 .5rem 0 0;

  /* remove IOS background */
  background-color: var(--background);
}
Hide radio native control

We now need to replace the control with our own. We will start with the unchecked style, and add to the styles to accomplished the checked look.

Unchecked Radio

We set the display to inline-block to allow us to dictate a width and height for the input which we will base on the font size in order for the radio to scale with text as it is grown or shrunk (Listing 2).

Note: Notice the currentColor value used for the color property. This specifically sets the color value for the input to the color value applied to the parent [3].

input[type="radio"] {
  ...
  display: inline-block;
  width: 1em;
  height: 1em;
  /* inherit the font from the parent so that we are sure the font-family, size ... match */
  font: inherit;
  color: currentColor;
}
Setting up the size and font

We now style the field to look like an unchecked ratio button using borders and border radius (Listing 3).

Note: Although we could have used inherit above to set the color. If we want the border to be the color of the text, but don't know the color of the text, we can't inherit. We can however use currentColor to set the value. The computed value will be the color property value of the parent element [3].

input[type="radio"] {
  ...
  border: 0.1em solid currentColor;
  border-radius: 50%;

  /* Adjust the position relative to the text */
  transform: translateY(.125rem);
}
Setting up the size and font

Figure 2 shows our current output. We have a style unchecked radio, however we can't tell which one is checked. We now need to handle the styles for the selected input.

Styled radio buttons. Label preceded by circle which is the same color as the label (black). Diameter is slightly larger than a capital letter for the text.
Unchecked radio buttons

Checked Radio

When the input is checked we want the color of the input to be that of our accent color. We also want there to be an inner circle within our input. Instead of adding a circle inside, we are going to create the illusion of a circle instead. We give the input a background color and then use box-shadow to create a circle inside the input. We make the color of the circle the same as our background which creates the illusion of transparency. The code applied to checked radios can be found in Listing 4.

input[type="radio"]:checked {
  /* change our border color to burgundy */
  border-color: var(--accent);
  /* makes the input burgundy */
  background-color: var(--accent);
  /* creates a white ring inside of the input */
  box-shadow: inset -.005rem -.005rem 0 .1rem var(--background);
}
Checked Radio

Our radio are now styled (Figure 3) but we still need to handle hover and focus.

Checked radio has a burgundy outline and inner burgundy circle
accent-color applied to radio buttons

Hover

Adding a hover effect will help our users understand that the element can be interacted with, and confirm which element the user is about to select. We are going to add a gray inner circle to the element when it is being hovered as seen in Figure 4.

The input being hovered's outline is black, same as the text, but the inner circle is gray.
Hover effect applied to "Water" option

To apply the effect, we use the pseudo class :hover and apply a background color and box-shadow similarly to what we did for checked but using a gray color rather than the accent color. Listing 5 shows the full CSS rule for the hover.

We only apply the hover to non checked inputs because we do not want to visually lose the concept of which one is currently already checked.

input[type="radio"]:not(:checked):hover {
  background-color: rgba(0, 0, 0, .36);
  box-shadow: inset -.005rem -.005rem 0 .1rem var(--background);
}
Hover effect

With hover handled, we will now handle styles to be applied when the element is focused.

Focus

Adding a visual effect when an element is in focus is imperative to accessibility and a requirement in the Web Content Accessibility Guideline (WCAG) standards. Success Criterion 2.4.7 Focus Visible states:

Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible [4].

By default, the browser will add an outline around the element. We are going to style that outline to match our style and color. We will then offset the outline away from the element to make it more obvious when present. Like hover, we will use a pseudo class to target the element, but this time we will use :focus-within to which we will add the new styles.

:focus-visible works a little bit differently then :focus as it only shows when the browser detects that the element is being navigated to or interacted with via keyboard rather than mouse [5]. Listing 6 shows this concept applied in code.

input[type="radio"]:focus-visible {
  outline: dotted 2px var(--accent);
  outline-offset: 3px;
}
Focus Effect

When we click on the radio value we would like to select, the focus outline does not appear. However, when we navigate to the selection via the keyboard, the outline appears as shown in Figure 5.

The second option, which is being focused has a burgundy dotted outline
Focus effect applied to "Tea" option

We added hover and focus effects, let's animate the change to make the visual transition between states smoother (Listing 7). On the input itself we will dictate which properties will be transitioned, the speed at which the transition happens, and the amount of time it will take to perform the transition.

input[type="radio"] {
  ...
  transition-property: background-color, border-color, outline, outline-offset;
  transition-timing-function: ease-in;
  transition-duration: 200ms;
}
Transition

Figure 6 shows the transition from state to state as the user interacts with the radio buttons.

As the user hovers, clicks, or keyboard navigates to each element, the change is applied smoothly over a 200 ms time period rather than abruptly
Applied transition

We aren't quite done yet. We have two more things we need to consider.

Reduced Motion

First is that since we applied an animation, we need to make sure to consider the user's preferences and suppress the animation for users who have prefers-reduced-motion set to reduce. One of the reasons a user may choose this setting is because on screen motion may render them ill. For accessibility and a good user experience it is imperative that we respect the user's choice. To accomplish this end, we use a media query to target the setting and set the transition duration to 0, which is the equivalent to removing it entirely (Listing 8).

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0ms; }
}
Reduced Motion

Forced Colors

Forced colors is a setting which enforces a user chosen limited color palette to the page [6]. It also affects some of the CSS properties we have applied to our input field, most notably box-shadow and background-color. To make sure the checked input is still differentiated from its unchecked counterpart, we add a background color of ActiveText to the input when checked. We loose the inner ring (as seen in Figure 7), but this ensures that the checked input is distinct.

Output when forced colors is applied

ActiveText references the system value for selected text as defined by the CSS Color Module Level 4 [7]. The color that will display will therefore depend upon the user's system settings.

To only apply this change to users who have choose to use forced-colors we use a media query as shown in Listing 9.

@media (forced-colors: active) {
  input[type="radio"]:checked {
    background-color: ActiveText;
  }
}
Forced Colors

With this last piece completed we have custom styled radio inputs that are accessible and respect our user's settings.

The code can be found below or on codepen at https://codepen.io/martine-dowden/pen/oNENMgw

See the Pen CSS Radio Buttons by Martine Dowden (@martine-dowden) on CodePen.

Happy Coding!

References

[1] Mozilla. “accent-color.” MDN, 4 April 2022, https://developer.mozilla.org/en-US/docs/Web/CSS/accent-color. Accessed 1 May 2022.

[2] Deveria, Alexis. “accent-color.” CanIUse, https://caniuse.com/?search=accent-color. Accessed 1 May 2022.

[3] Coyier, Chris. “currentColor.” CSS-Tricks, 17 March 2011, https://css-tricks.com/currentcolor/. Accessed 1 May 2022.

[4] W3C. “Web Content Accessibility Guidelines (WCAG) 2.1.” W3C, 5 June 2018, https://www.w3.org/TR/WCAG21/#focus-visible. Accessed 2 May 2022.

[5] Mozilla. “:focus-visible.” MDN, https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible. Accessed 2 May 2022.

[6] Mozilla. “forced-colors.” MDN, 18 February 2022, https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors. Accessed 2 May 2022.

[7] W3C. “CSS Color Module Level 4.” W3C, 28 April 2022, https://www.w3.org/TR/css-color-4/#css-system-colors. Accessed 2 May 2022.

Specialties

Overview of our specialties including: accessibility, angular, CSS, design, and Firebase

License: CC BY-NC-ND 4.0 (Creative Commons)