Toggling CSS Custom Properties with Radio Buttons

As part of a recent article published on Codrops, I made some demos that allowed the user to toggle between three different values for a clip path using radio buttons. As with so many things these days, I found myself reaching for custom properties! Let’s take a look at why custom properties are great for this.

Common approaches

Let’s look at one way we could approach this without custom properties. We could use Javascript to detect when a user interacts with a radio button and append a class to the <figure> accordingly:

const clippedElement = document.querySelector('.clipped-element')
const controls = document.querySelector('.controls')

let currentClass = 'circle'

const onChange = (e) => {
if (!e.target.value || !e.target.checked) return

if (clippedElement.classList.contains(currentClass)) {
clippedElement.classList.replace(currentClass, e.target.value)
} else {
clippedElement.classList.add(e.target.value)
}

currentClass = e.target.value
}

controls.addEventListener('change', onChange)

Then it’s a matter of defining our clip-path values for each class within our CSS:

.clipped-element {
-webkit-clip-path: circle(25% at 70%);
clip-path: circle(25% at 50%);
}

.clipped-element.polygon {
-webkit-clip-path: polygon(50% 0%...);
clip-path: polygon(50% 0%...);
}

.clipped-element.svg {
-webkit-clip-path: path('M202.2...');
clip-path: path('M202.2...');
}

I’ve shortened the values of the last two here for brevity and readability, but this demo has the full code.

Another option is to set the style attribute in JS, but I prefer the CSS way, as it feels cleaner to me. clip-path requires a prefix in some browsers, so setting this in CSS seems more maintainable — but it’s a matter of personal preference.

While there’s nothing wrong with these approaches, let’s see what the alternative looks like with custom properties.

Toggling the custom property

In our HTML we have three radio buttons with values of circle, polygon and svg respectively:

<form class="controls">
<fieldset>
<legend>Switch clip path values</legend>
<div>
<input type="radio" name="shape" id="r_circle" value="circle" checked />
<label for="r_circle">Circle</label>
</div>
<div>
<input type="radio" name="shape" id="r_polygon" value="polygon" />
<label for="r_polygon">Polygon</label>
</div>
<div>
<input type="radio" name="shape" id="r_svg" value="svg" />
<label for="r_svg">SVG path</label>
</div>
</fieldset>
</form>

<div class="clipped-element"></div>

We can assign a custom property for each value in CSS (again, the SVG path and polygon values are truncated here for brevity):

.clipped-element {
--circle: circle(25% at 50%);
--polygon: polygon(50% 0%...);
--svg: path('M202.2...');
}

Additionally, we can assign a custom property of --clip a value for our clip path. That means that every time we change the --clip value, both the prefixed property value and the regular property value will be updated. (I’ve written about this in a previous article, 7 Uses For Custom Properties.)

We’ll give it an initial value corresponding to our first radio button option, using the --circle custom property:

.clipped-element {
--circle: circle(25% at 70%);
--polygon: polygon(50% 0%...);
--svg: path('M202.2...');

--clip: var(--circle);
-webkit-clip-path: var(--clip);
clip-path: var(--clip);
}

Then, in our onChange event handler, we update the value of --clip with the custom property corresponding to the selected radio input. We can use template literals to do this:

const onChange = (e) => {
if (!e.target.value || !e.target.checked) return

clippedElement.style.setProperty('--clip', `var(--${e.target.value})`)
}

We achieve the same result, and both our CSS and JS code is much more concise.

The full demo:

See the Pen Clip path toggle with custom properties by Michelle Barker (@michellebarker) on CodePen.

Incidentally, CSS Tricks has recently published a complete guide to custom properties, featuring some great tips and examples of ways to use them.