In this tutorial, we’ll build a 100% CSS solution for content toggles in Bricks.
Table of Contents
Introduction
If you are looking for an integrated solution in Bricks to switch visibility between different elements – such as pricing tables or features – with a simple toggle, look no further and purchase BricksExtras. But if you’re willing to get your hands dirty and dive into custom CSS, then this tutorial might be useful!
The DOM structure
First of all, recreate the following DOM structure and assign the classes for each element as follow:

Note that the first element is a code block. The revealed-items can be any element.
Pure CSS method
The HTML
Paste the following HTML snippet in the code block:
<fieldset class="radio-switch">
<!-- First Item -->
<input type="radio" name="switch" id="item1">
<label for="item1">Label 1</label>
<!-- Second Item -->
<input type="radio" name="switch" id="item2" checked>
<label for="item2">Label 2</label>
<!-- Third Item -->
<input type="radio" name="switch" id="item3">
<label for="item3">Label 3</label>
<div class="highlight"></div>
</fieldset>
Feel free to change the label as per your needs, but don’t modify the attributes or it will break our animation.
In our example, we used 3 different labels/input but the CSS snippet supports up to 4 natively. To adjust that number to your needs, just delete/add more items as per your need.
The second label is the default active label on load because it has the attribute checked at the end of the input tag. Feel free to position it on any item of your choice.
The CSS
Paste the following CSS code to your page:
/* CSS Variables */
fieldset.radio-switch {
--switch-item-number: 3;
--switch-padding: 5px;
--switch-bg: #fff;
--switch-border-width: 1px;
--switch-border-color: #ccc;
--switch-border-radius: 50px;
--switch-label-padding: 1.5rem 0.5rem;
--switch-inactive-text-color: #666;
--switch-active-text-color: #fff;
--switch-active-bg-color: #350792;
}
/* Fieldset*/
fieldset.radio-switch {
display: flex;
position: relative;
padding: 0;
outline: 0;
border: var(--switch-border-width) solid var(--switch-border-color);
background-color: var(--switch-bg);
border-radius: var(--switch-border-radius);
overflow: hidden;
width: 100%;
}
/* Hide Imputs*/
fieldset.radio-switch input {
visibility: hidden;
opacity: 0;
display: none !important;
}
/* Labels */
fieldset.radio-switch label {
z-index: 1;
margin: 0;
padding: var(--switch-label-padding);
border-radius: var(--switch-border-radius);
cursor: pointer;
color: var(--switch-inactive-text-color);
font-size: 14px;
line-height: 1.4em;
font-weight: 600;
}
/* Active Labels*/
fieldset.radio-switch input:checked+label {
color: var(--switch-active-text-color);
cursor: default;
}
/* Highlight */
fieldset.radio-switch label,
fieldset.radio-switch .highlight {
width: calc(100% / var(--switch-item-number));
display: flex;
align-items: center;
justify-content: center;
text-align: center;
transition: 300ms transform ease-in-out,
300ms color ease-in-out;
}
fieldset.radio-switch .highlight {
position: absolute;
background-color: var(--switch-active-bg-color);
top: calc(var(--switch-padding) / 2);
left: calc(var(--switch-padding) / 2);
bottom: 0;
right: 0;
border-radius: var(--switch-border-radius);
height: calc(100% - var(--switch-padding));
width: calc((100% / var(--switch-item-number)) - calc(var(--switch-padding) / var(--switch-item-number)));
}
/* Moving Highliht*/
fieldset.radio-switch input:nth-of-type(2):checked~.highlight {
transform: translateX(100%);
}
fieldset.radio-switch input:nth-of-type(3):checked~.highlight {
transform: translateX(200%);
}
fieldset.radio-switch input:nth-of-type(4):checked~.highlight {
transform: translateX(300%);
}
/* Revealed Items */
body.bricks-is-frontend .revealed-items {
display: none !important;
}
/* Active Reveald Item */
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(1):checked)~.revealed-items.item1,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(2):checked)~.revealed-items.item2,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(3):checked)~.revealed-items.item3,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(4):checked)~.revealed-items.item4 {
display: flex !important;
animation: 300ms activeItems ease-in-out;
}
/* Custom Entrance Animation */
@keyframes activeItems {
0% {
transform: translateY(40px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
Note that the first section is a set of useful CSS variables so you can easily style the toggle as you need. The first variable –switch-item-number is mandatory and represents the number of labels inside your toggle. Since we used 3 different labels, we put 3 inside the variable. If you add or remove items from the HTML markup, update the correct number inside this variable as well.
Stack the switcher on mobile (optional)
If you’d like to vertically stack the switcher on mobile, add the following CSS code:
@media screen and (max-width: 767px) {
fieldset.radio-switch {
--switch-border-radius: 15px;
flex-direction: column;
}
/* Highlight */
fieldset.radio-switch .highlight,
fieldset.radio-switch label {
width: calc(100% - var(--switch-padding));
height: calc((100% / var(--switch-item-number)) - calc(var(--switch-padding) / var(--switch-item-number)));
}
/* Moving Highliht*/
fieldset.radio-switch input:nth-of-type(2):checked~.highlight {
transform: translateY(100%);
}
fieldset.radio-switch input:nth-of-type(3):checked~.highlight {
transform: translateY(200%);
}
fieldset.radio-switch input:nth-of-type(4):checked~.highlight {
transform: translateY(300%);
}
}
Firefox compatibility
Unfortunately, Firefox doesn’t support the :has() pseudo-element. To make it compatible, we need to apply some changes.
First of all, add an active class to the visible revealed item you want to show on load:

Then modify the CSS code as follow:
/* Active Reveald Item */
/*
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(1):checked)~.revealed-items.item1,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(2):checked)~.revealed-items.item2,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(3):checked)~.revealed-items.item3,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(4):checked)~.revealed-items.item4 {
display: flex !important;
animation: 300ms activeItems ease-in-out;
}*/
body.bricks-is-frontend .revealed-items.active{
display: flex !important;
animation: 300ms activeItems ease-in-out;
}
Ultimately, add the following JavaScript script to your page:
// Load the script when the DOM is loaded
window.addEventListener('DOMContentLoaded', () => {
// Query all the switchers on the page
const radioGroups = document.querySelectorAll('.switcher-wrapper');
// stop the script if there is no switcher
if (radioGroups.length < 1) return;
// Function to get all the siblings of an element
const getSiblings = (elem) => {
return Array.prototype.filter.call(elem.parentNode.children, (sibling) => {
return sibling !== elem;
});
};
// Loop in each switcher groups
radioGroups.forEach((radioGroup) => {
// Query the inputs
const radios = radioGroup.querySelectorAll('input');
// Query the revealed items
const revealedItems = getSiblings(radioGroup);
// Loop into each radio groups
radios.forEach((radio, index) => {
// Listener on click
radio.addEventListener('click', () => {
// Get the revealed item
const revealedItem = revealedItems[index];
// Remove the active class on all revealed items
revealedItems.forEach(el => el.classList.remove('active'));
// Add the active class to the active revealed item
revealedItem.classList.add('active');
})
})
})
})
Conclusion
If you set everything correctly, you should see a similar result on frontend:
Note that this solution is not accessible out of the box. If accessibility is mandatory for your project, consider using another solution or be prepared to add some extra work!
1 comment
Amanda
Great tutorial Maxime. Wondering if it could be adapted to build a pricing table like this one: https://www.3cx.com/ordering/pricing/