Custom Radio Buttons
Modern custom radio buttons with a clean dark design, smooth selection animation, and a glowing active state.
<div class="custom-radio-group">
<label class="custom-radio">
<input
type="radio"
name="plan"
value="basic"
checked
>
<span class="radio-circle"></span>
<span class="radio-text">
Basic
</span>
</label>
<label class="custom-radio">
<input
type="radio"
name="plan"
value="pro"
>
<span class="radio-circle"></span>
<span class="radio-text">
Pro
</span>
</label>
<label class="custom-radio">
<input
type="radio"
name="plan"
value="premium"
>
<span class="radio-circle"></span>
<span class="radio-text">
Premium
</span>
</label>
</div>
.custom-radio-group{
display:flex;
align-items:center;
gap:24px;
flex-wrap:wrap;
}
.custom-radio{
position:relative;
display:inline-flex;
align-items:center;
gap:10px;
color:#c7d2e5;
font-size:15px;
cursor:pointer;
user-select:none;
}
.custom-radio input{
position:absolute;
opacity:0;
pointer-events:none;
}
.radio-circle{
width:22px;
height:22px;
flex:0 0 22px;
position:relative;
border:2px solid #33445f;
border-radius:50%;
background:#0b1423;
transition:.25s ease;
}
.radio-circle::after{
content:"";
position:absolute;
top:50%;
left:50%;
width:10px;
height:10px;
border-radius:50%;
background:#22d3ee;
transform:translate(-50%, -50%) scale(0);
transition:.2s ease;
}
.custom-radio input:checked + .radio-circle{
border-color:#22d3ee;
box-shadow:
0 0 16px rgba(34,211,238,.25);
}
.custom-radio input:checked + .radio-circle::after{
transform:translate(-50%, -50%) scale(1);
}
.custom-radio:hover .radio-circle{
border-color:#22d3ee;
}
.custom-radio input:focus-visible + .radio-circle{
box-shadow:
0 0 0 3px rgba(34,211,238,.12);
}
.radio-text{
line-height:1.4;
}
How These Custom Radio Buttons Work
These Custom Radio Buttons use native HTML radio inputs for selection while CSS replaces the browser’s default visual appearance with custom circular controls. The component provides three options—Basic, Pro, and Premium—and places them inside a .custom-radio-group container.
Each option is wrapped in a .custom-radio label. Inside the label is a native <input type="radio">, a .radio-circle span that creates the visible control, and a .radio-text span containing the option name. Because each radio input is inside its label, users can click either the custom circle or its text to select that option.
All three inputs use the same name="plan" value. This is important because HTML treats radio buttons sharing the same name as one selection group. Choosing Pro automatically deselects Basic, for example. The Basic input includes the checked attribute, so it is selected when the component first loads.
The native radio inputs are visually hidden with position: absolute, opacity: 0, and pointer-events: none. They still maintain the actual checked state, while the .radio-circle element provides the custom appearance.
The outer radio circle measures 22 Ă— 22px. It uses a 2px #33445f border, a dark #0b1423 background, and border-radius: 50%. The circle also has flex: 0 0 22px, which prevents it from shrinking when placed beside longer text.
The selected indicator is created without additional HTML. .radio-circle::after produces a 10 Ă— 10px cyan circle positioned in the exact center of the outer control. By default, it uses transform: translate(-50%, -50%) scale(0), so the indicator is effectively hidden.
When a radio input becomes selected, input:checked + .radio-circle changes the outer border to cyan #22d3ee and adds a subtle cyan glow. Another checked-state selector changes the pseudo-element to scale(1). The .2s ease transition creates the small scaling animation as the selected dot appears. For more information about styling selected form controls, see the MDN :checked documentation.
The component also includes hover and keyboard-focus feedback. Hovering over a .custom-radio changes the circle’s border to cyan. When the hidden input receives :focus-visible, its adjacent circle receives a three-pixel cyan-tinted focus ring through box-shadow.
The group itself uses Flexbox with a 24px gap and flex-wrap: wrap. This allows the radio options to move onto another row when there is not enough horizontal space. No JavaScript is included because native radio-button behavior already handles exclusive selection.
Customizing the Custom Radio Buttons
The existing HTML and CSS provide several straightforward values for adapting the radio group to another form or selection interface.
- Change the 24px
.custom-radio-groupgap to increase or decrease spacing between options. - Edit the 22 Ă— 22px
.radio-circledimensions to resize the outer radio controls. - Change the 10 Ă— 10px
::afterdimensions to resize the selected inner dot. - Replace
#22d3eeto customize the selected, hover, and focus color. - Change
#33445fto modify the default border color. - Edit
#0b1423to use a different background inside each radio circle. - Adjust the
.25souter transition or.2sinner-dot transition to change the interaction speed. - Replace Basic, Pro, and Premium and update their
valueattributes to represent different choices while keeping the samenamewhen they should remain one radio group.
Where to Use These Custom Radio Buttons
These controls work best when users need to choose exactly one option from a small set of mutually exclusive choices.
- Subscription plan selectors
- Billing period choices
- Shipping method selection
- Account type forms
- Payment method selectors
- Survey and questionnaire options
- Product configuration choices
If a setting needs a simple on/off state instead of choosing between several options, the Toggle Switch is a more appropriate related CodeRipple component. These radio buttons already provide native selection behavior, so additional JavaScript is only necessary if an application needs to react to the selected value.
Frequently Asked Questions
Do these Custom Radio Buttons use JavaScript?
No. The retrieved component contains no JavaScript. Selection is handled by native HTML radio behavior, while CSS styles the checked, hover, and keyboard-focus states.
Why can only one radio option be selected?
All three inputs use name="plan". Radio inputs with the same name belong to the same group, so selecting one automatically deselects another.
How is the cyan dot inside the selected radio created?
The inner dot comes from the .radio-circle::after pseudo-element. It starts at scale(0) and changes to scale(1) when the preceding radio input is checked.
Which option is selected by default?
Basic is selected initially because its radio input contains the checked attribute. Pro and Premium do not have that attribute.
These Custom Radio Buttons preserve the native behavior of HTML radio inputs while using CSS to provide custom circles, animated selection indicators, hover feedback, focus styling, and flexible spacing.
