Radio buttons as images: three wallpaper thumbnails painted with layered CSS gradients. An outline ring springs out around the pick, and the chosen wallpaper slowly drifts as if it were live.
Also known as: radio button images, radio buttons with images css, image radio button picker, wallpaper picker css.
<div class="rad-wallpaper-image-radio" role="radiogroup" aria-label="Wallpaper"> <label><input type="radio" name="rad-wallpaper-image-radio" value="dune"><span class="rad-wallpaper-image-radio-pic rad-wallpaper-image-radio-dune" aria-hidden="true"></span><span class="rad-wallpaper-image-radio-name">Dune</span></label> <label><input type="radio" name="rad-wallpaper-image-radio" value="aurora" checked><span class="rad-wallpaper-image-radio-pic rad-wallpaper-image-radio-aurora" aria-hidden="true"></span><span class="rad-wallpaper-image-radio-name">Aurora</span></label> <label><input type="radio" name="rad-wallpaper-image-radio" value="tide"><span class="rad-wallpaper-image-radio-pic rad-wallpaper-image-radio-tide" aria-hidden="true"></span><span class="rad-wallpaper-image-radio-name">Tide</span></label> </div>
.rad-wallpaper-image-radio {
--c: var(--rad-color, #3b82f6);
font-size: var(--rad-size, 18px);
display: inline-flex;
gap: 1em;
font-family: system-ui, sans-serif;
line-height: 1.2;
}
.rad-wallpaper-image-radio label {
position: relative;
display: grid;
justify-items: center;
gap: 0.65em;
cursor: pointer;
}
.rad-wallpaper-image-radio input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.rad-wallpaper-image-radio-pic {
position: relative;
display: block;
width: 5.2em;
height: 3.4em;
overflow: hidden;
border-radius: 0.6em;
box-shadow: 0 0.3em 0.7em -0.4em rgb(0 0 0 / 0.35);
outline: 0.13em solid transparent;
outline-offset: 0;
transition: outline-color 0.2s ease, outline-offset 0.45s cubic-bezier(0.34, 1.56, 0.64, 1), transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* the picture itself, on a layer that can drift inside the rounded frame */
.rad-wallpaper-image-radio-pic::before {
content: "";
position: absolute;
inset: 0;
filter: saturate(0.75) brightness(0.97);
transition: filter 0.3s ease, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.rad-wallpaper-image-radio-pic::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18);
}
.rad-wallpaper-image-radio-dune::before {
background:
radial-gradient(130% 34% at 40% 106%, #9a3412 0 97%, transparent 100%),
radial-gradient(80% 48% at 88% 100%, #c2410c 0 97%, transparent 100%),
radial-gradient(95% 58% at 12% 100%, #ea580c 0 97%, transparent 100%),
radial-gradient(circle at 72% 32%, #fffbeb 0 0.42em, transparent 0.5em),
linear-gradient(#fef3c7, #fdba74);
}
.rad-wallpaper-image-radio-aurora::before {
background:
radial-gradient(55% 75% at 22% 35%, #22d3ee, transparent 70%),
radial-gradient(55% 70% at 78% 30%, #a78bfa, transparent 70%),
radial-gradient(80% 55% at 50% 105%, #34d399, transparent 70%),
linear-gradient(#0b1026, #111c3f);
}
.rad-wallpaper-image-radio-tide::before {
background:
radial-gradient(140% 30% at 50% 108%, #1e3a8a 0 97%, transparent 100%),
radial-gradient(75% 46% at 85% 100%, #2563eb 0 97%, transparent 100%),
radial-gradient(90% 56% at 15% 100%, #38bdf8 0 97%, transparent 100%),
radial-gradient(circle at 30% 30%, #ffffff 0 0.3em, transparent 0.38em),
linear-gradient(#f0f9ff, #bae6fd);
}
.rad-wallpaper-image-radio-name {
font-size: 0.78em;
font-weight: 600;
color: color-mix(in srgb, currentColor 55%, transparent);
transition: color 0.25s ease;
}
.rad-wallpaper-image-radio label:hover .rad-wallpaper-image-radio-pic {
transform: translateY(-0.1em);
}
.rad-wallpaper-image-radio label:hover .rad-wallpaper-image-radio-pic::before {
filter: none;
}
.rad-wallpaper-image-radio input:checked + .rad-wallpaper-image-radio-pic {
outline-color: var(--c);
outline-offset: 0.2em;
}
.rad-wallpaper-image-radio input:checked + .rad-wallpaper-image-radio-pic::before {
filter: none;
animation: rad-wallpaper-image-radio-drift 6s ease-in-out infinite alternate;
}
.rad-wallpaper-image-radio input:checked ~ .rad-wallpaper-image-radio-name {
color: currentColor;
}
.rad-wallpaper-image-radio input:focus-visible ~ .rad-wallpaper-image-radio-name {
outline: 0.12em solid var(--c);
outline-offset: 0.25em;
border-radius: 0.2em;
}
@keyframes rad-wallpaper-image-radio-drift {
from { transform: scale(1.04); }
to { transform: scale(1.16) translate(-4%, -3%); }
}
@media (prefers-reduced-motion: reduce) {
.rad-wallpaper-image-radio input:checked + .rad-wallpaper-image-radio-pic::before {
animation: none;
}
}
@media (prefers-reduced-motion: reduce) {
.rad-wallpaper-image-radio, .rad-wallpaper-image-radio *, .rad-wallpaper-image-radio::before, .rad-wallpaper-image-radio::after, .rad-wallpaper-image-radio *::before, .rad-wallpaper-image-radio *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--rad-color on the element or a parent.
Currently previewing #3b82f6.--rad-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 18px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.