Оригинал примера: https://stackoverflow.com/questions/3772273/pure-css-checkbox-image-rep…
-
Конструкция для чекбоксов и радиокнопок схожи, за исключением отображения самих элементов формы.
По этой причине разбор идет по одному элементу (чекбоксы). Второй (радиокнопки) добавим в самом конце, копированием html кода.<!-- чекбоксы --> <input type="checkbox" id="myCheckbox-1" /> <input type="checkbox" id="myCheckbox-2" /> <!-- радиокнопки --> <input type="radio" name="myRadio" id="myRadio-1" /> <input type="radio" name="myRadio" id="myRadio-2" /> -
Стоит заранее приготовить изображения или привести их к одинаковой ширине или пропорции высота/ширина.
Указанный источник медиа отсутствует и требует повторной вставки.Указанный источник медиа отсутствует и требует повторной вставки.
Шаг 1. Создаем форму с чекбоксами
- Создаем форму с чекбоксами
- Под каждым чекбоксом размещаем
<label>с картинкой
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Checkbox & Radio buttons</title>
</head>
<body>
<div>
<div>Checkbox</div>
<div>
<div>
<input type="checkbox" id="myCheckbox-1" />
<label for="myCheckbox-1">
<img
src="/sites/default/files/2026-01/2021-mazda-mx-5-miata-mmp-1-1593459650.avif"
/>
</label>
</div>
<div>
<input type="checkbox" id="myCheckbox-2" />
<label for="myCheckbox-2">
<img
src="/sites/default/files/2026-01/2020-chevrolet-corvette-c8-102-1571146873.avif"
/>
</label>
</div>
</div>
</div>
</body>
</html>Шаг 2. Добавляем классы.
<div class="cont-bg">
<div class="cont-title">Checkbox</div>
<div class="cont-main">
<div class="cont-checkbox">
<input type="checkbox" id="myCheckbox-1" />
<label for="myCheckbox-1">
<img
src="/sites/default/files/2026-01/2021-mazda-mx-5-miata-mmp-1-1593459650.avif"
/>
</label>
</div>
<div class="cont-checkbox">
<input type="checkbox" id="myCheckbox-2" />
<label for="myCheckbox-2">
<img
src="/sites/default/files/2026-01/2020-chevrolet-corvette-c8-102-1571146873.avif"
/>
</label>
</div>
</div>
</div>
Шаг 3. Добавляем стили.
- Скрываем чекбоксы
- Выравниваем картинки, заголовок с помощью flexbox
<style>
/**
* Основной контейнер.
* Выравниваем заголовок и содержимое по центру
*/
.cont-bg {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* Заголовок. Отступ снизу */
.cont-title {
margin-bottom: 1rem;
}
/**
* Контейнер с чекбоксами и изображениями.
* Выравниваем изображения в линию,
* добавляем отступ
*/
.cont-main {
display: flex;
flex-wrap: wrap;
align-content: center;
justify-content: center;
gap: 0.75rem;
}
/**
* Карточка из одного элемента (чекбокс/радиокнопка)
* Задаем размеры карточки
*/
.cont-checkbox {
width: 300px;
}
/**
* Скрываем все элементы формы (чекбоксы, радиокнопки)
*/
input {
display: none;
}
/* Контейнер картинки */
label {
display: block;
width: 100%;
height: 100%;
cursor: pointer; /* вид курсор при наведении на элемент - указатель */
opacity: 0.6; /* прозрачность: от 0.0 (полностью прозрачный, невидимый) до 1.0 (полностью непрозрачный). */
}
/**
* Настройки изображения
*/
label img {
width: 100%;
filter: grayscale(100%); /* обесцвечивание - полностью серое (чёрно-белое) изображение */
}
</style>
Примечания.
- По спецификации HTML/CSS
<label>- inline-элемент (как<span>,<a>,<b>) и предназначен для обозначения поля формы текстом.
Чтобы превратить<label>из текстовой метки в интерактивный UI компонент (карточку), к<label>выставляетсяdisplay: block;илиdisplay: inline-block;Оба варианта подходят.
Но! По умолчанию элементыinlineиinline-blockвыравниваются по базовой линии шрифта (vertical-align: baseline).- Картинка встает на линию, на которой пишутся буквы.
- Ниже этой линии браузер резервирует несколько пикселей для «хвостиков» букв (таких как «ц», «щ», «y», «g»).
- Даже если текста нет, этот пустой зазор (обычно 3-5px) добавится к высоте родительского элемента со значением
display: inline-block;.
- Заметка с различными примерами о свойстве
filter
Шаг 4. Применяем стили для изображений с выбранным чекбоксом
input:checked + label {
opacity: 1;
box-shadow: 0 0 0 3px #ffc107;
}
input:checked + label img {
filter: none;
}
После шага 4 имеем:
Шаг 5. Добавляем SVG с галочкой
Код SVG картинки
<svg viewBox="0 0 12 10">
<polyline points="1.5 6 4.5 9 10.5 1"></polyline>
</svg>
Примечания.
viewBoxзадаёт логическую (внутреннюю) систему координат холста SVG.viewBox = "0 0 12 10"- 0 0 - координаты левого верхнего угла холста
- 12 - ширина холста (максимальное значение по X)
- 10 - высота холста (максимальное значение по Y)
points="1.5 6 4.5 9 10.5 1"— задает координаты трёх соединенных точек, т.е. на самом деле это именно галочка.
Но из-за заливки (по умолчанию), браузер автоматически замыкает фигуру, и получается треугольник.
Добавляем svg картинку сразу после каждого тега <img>
<label for="myCheckbox-1">
<img
src="/sites/default/files/2026-01/2021-mazda-mx-5-miata-mmp-1-1593459650.avif"
/>
<span class="cover-checkbox">
<svg viewBox="0 0 12 10">
<polyline points="1.5 6 4.5 9 10.5 1"></polyline>
</svg>
</span>
</label>Добавленные стили
* {
/* переменные */
--transition: 0.15s;
--orange: #ffc107;
}
input:checked + label .cover-checkbox {
opacity: 1; /* возвращаем непрозрачность */
transform: scale(1); /* масштабирование 100% */
}
input:checked + label .cover-checkbox svg {
stroke-dashoffset: 0; /* убираем сдвиг пробелов (см. примечание) */
}
label {
/*
без этого значения дочерний абсолютно позиционированный элемент
будет располагаться относительно ближайшего позиционированного предка,
т.е. области просмотра (в нашем случае)
*/
position: relative;
}
label .cover-checkbox {
position: absolute;
right: 5px;
top: 3px;
z-index: 1; /* приоритет при наложении слоев */
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--orange);
border: 2px solid #fff;
transition: transform var(--transition),
opacity calc(var(--transition) * 1.2) linear;
opacity: 0;
transform: scale(0);
}
label .cover-checkbox svg {
width: 13px;
height: 11px;
display: inline-block;
vertical-align: top;
fill: none; /* убираем закрашивание по умолчанию, чтобы получить галочку, а не треугольник */
margin: 5px 0 0 3px;
stroke: #fff; /* цвет контура (можно применять к шрифтам с интересными эффектами) */
stroke-width: 2; /* ширина контура */
stroke-linecap: round; /* Округляет концы линии. */
stroke-linejoin: round; /* Округляет углы между стыками (улог галочки) */
stroke-dasharray: 16px; /* маска линия-пробел (см. примечание) */
transition: stroke-dashoffset 0.4s ease var(--transition);
stroke-dashoffset: 16px; /* сдвиг пробелов (см. примечание) */
}
Примечания.
stroke-dasharrayиstroke-dashoffset
- В графике часто приходится рисовать пунктирные линии (чертежи, карты, схемы). Для этих целей ввели свойство
stroke-dasharray.
Буквально значениеstroke-dasharray: 16px;говорит следующее: рисуй линию длиной 16, потом пропуск 16, повторить.
Пример:stroke-dasharray: 10px 5px 2px 5px;— штрих 10 → пробел 5 → штрих 2 → пробел 5 → и повтор...
-
stroke-dashoffset— сдвигает начала отрисовки. Как если бы мы начали рисовать не с 0, а с n-ого пиксела.<svg viewBox="0 0 100 20" style="stroke-dasharray: 20px 5px; stroke-dashoffset: 10px; stroke: green; background: aliceblue;"> <polyline points="0 10 100 10"></polyline> </svg> - значения единиц измерения
stroke-dasharrayиstroke-dashoffsetчасто пишут вpx, но можно и не указывать, т.к. в svg расчет идет в единицах svg
- 3. Из пунктов 1 и 2: если длина линии меньше
stroke-dasharrayиstroke-dasharray=stroke-dashoffset, то элемент будет невидим!
- С помощью изменения свойства
stroke-dashoffsetи анимации отображается прорисовка галочки слева-направо.transition: stroke-dashoffset 0.4s ease var(--transition);
- браузер отслеживает изменениеstroke-dashoffsetэлемента
- анимация занимает 0.4s или 400мс
- функция анимации:ease- медленно в начале, быстро в середине, в конце снова медленно
- перед стартом анимации есть задержка (после активации браузер ждет 0,15s или 150мс и ничего не делает)
- В графике часто приходится рисовать пунктирные линии (чертежи, карты, схемы). Для этих целей ввели свойство
-
label .cover-checkbox { transition: transform var(--transition), opacity calc(var(--transition) * 1.2) linear; opacity: 0; transform: scale(0); }Свойство
transitionопределяет
- изменение масштаба от 0 до 100% и наоборот происходит 0 за 150мс с функцией ease (действует по умолчанию)
- изменение прозрачности с 0 до 1 и наоборот происходит (150мс * 1.2) с функцией linear (равномерно)Выходит, полная непрозрачность наступает чуть позже установления 100% масштаба. Вот так придумали..
Результат после шага 5.
Шаг 6. Добавляем подписи с маркой машины.
Добавляем html с тексто с маркой машины
<div class="info">Mazda MX-5 Miata</div> в конец каждого <label>
Добавляем стили
/* задаем фон страницы */
.cont-bg {
background-image: radial-gradient(
circle farthest-corner at 7.2% 13.6%,
rgba(37, 249, 245, 1) 0%,
rgba(8, 70, 218, 1) 90%
);
}
/* Задаем высоту карточки и фон */
.cont-checkbox {
height: 200px;
background: white;
}
/**
* Настройки изображения
*/
label img {
clip-path: polygon(0% 0%, 100% 0%, 100% 81%, 50% 100%, 0% 81%);
}
/* стили подписи */
label .info {
text-align: center;
margin-top: 0.2rem;
font-weight: 600;
font-size: 0.8rem;
}
Примечания.
background-image: radial-gradient(circle farthest-corner at 7.2% 13.6%, rgba(37, 249, 245, 1) 0%, rgba(8, 70, 218, 1) 90%);- Функция
radial-gradientдля создания плавного перехода цветов от центра к краям (в отличие от линейногоlinear-gradient, который идёт в одном направлении). circle— градиент круглый, идет равномерноfarthest-corner— градиент распространяется до самого дальнего угла
Это гарантирует, что весь контейнер будет покрыт градиентом, даже если центр смещён.at 7.2% 13.6%— положение центра (7.2% по ширине и 13.6 по высоте)rgba(37, 249, 245, 1) 0%→ ярко-бирюзовый (почти неоновый) в центреrgba(8, 70, 218, 1) 90%→ глубокий синий ближе к краю
далее (от 90% до 100%) синий остаётся без изменений (градиент "замирает").
- Функция
clip-path: polygon(0% 0%, 100% 0, 100% 81%, 50% 100%, 0 81%);clip-path— это CSS-свойство, которое обрезает элемент по заданной форме. Всё, что находится вне указанной области, становится невидимым. Это мощный инструмент для создания нестандартных форм: треугольников, ромбов, «вырезанных» углов, значков и т.п.
Вpolygonрисуется 5угольник - первая координата левый верхний угол.
Результат после шага 6.
Шаг 7. Дополнительные стили
- скругление углов
* {
/* переменные */
--border-radius: 0.5rem;
}
.cont-checkbox {
border-radius: var(--border-radius);
}
/* Скругление углов*/
label {
border-radius: var(--border-radius);
overflow: hidden;
}
- цвет заголовка
/* Шрифт заголовока */
.cont-title {
color: white;
font-size: 1.25rem;
font-weight: 600;
}
- запрет перетаскивания картинки
Если клинкуть на изображение, при этом сместив курсор (как при перетаскивании), выделения элемента не произойдет.
Решается добавлением атрибута тегу<img>, запрещающего перетаскивание.<img src="/sites/default/files/2026-01/2021-mazda-mx-5-miata-mmp-1-1593459650.avif" draggable="false" /> - добавим атрибут
altвсем изображениям<img src="/sites/default/files/2026-01/2021-mazda-mx-5-miata-mmp-1-1593459650.avif" draggable="false" alt="mazda mx-5 miata" />
Итоговый вариант.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Checkbox & Radio buttons</title>
<style>
* {
/* переменные */
--transition: 0.15s;
--orange: #ffc107;
--border-radius: 0.5rem;
}
/**
* Основной контейнер.
* Выравниваем заголовок и содержимое по центру
*/
.cont-bg {
background-image: radial-gradient(
circle farthest-corner at 7.2% 13.6%,
rgba(37, 249, 245, 1) 0%,
rgba(8, 70, 218, 1) 90%
);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* Заголовок. Отступ снизу */
.cont-title {
color: white;
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1rem;
}
/**
* Контейнер с чекбоксами и изображениями.
* Выравниваем изображения в линию,
* добавляем отступ
*/
.cont-main {
display: flex;
flex-wrap: wrap;
align-content: center;
justify-content: center;
gap: 0.75rem;
}
/**
* Карточка из одного элемента (чекбокс/радиокнопка)
* Задаем размеры карточки
*/
.cont-checkbox {
width: 300px;
height: 200px;
border-radius: var(--border-radius);
background: white;
}
/**
* Скрываем все элементы формы (чекбоксы, радиокнопки)
*/
input {
display: none;
}
input:checked + label {
opacity: 1;
box-shadow: 0 0 0 3px var(--orange);
}
input:checked + label img {
filter: none;
}
input:checked + label .cover-checkbox {
opacity: 1;
transform: scale(1);
}
input:checked + label .cover-checkbox svg {
stroke-dashoffset: 0;
}
/* Контейнер картинки */
label {
display: inline-block;
width: 100%;
height: 100%;
cursor: pointer; /* вид курсор при наведении на элемент - указатель */
border-radius: var(--border-radius);
overflow: hidden;
position: relative;
opacity: 0.6; /* прозрачность: от 0.0 (полностью прозрачный, невидимый) до 1.0 (полностью непрозрачный). */
}
/**
* Настройки изображения
*/
label img {
width: 100%;
filter: grayscale(100%);
clip-path: polygon(0% 0%, 100% 0%, 100% 81%, 50% 100%, 0% 81%);
display: block;
}
label .cover-checkbox {
position: absolute;
right: 5px;
top: 3px;
z-index: 1;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--orange);
border: 2px solid #fff;
transition: transform var(--transition),
opacity calc(var(--transition) * 1.2) linear;
opacity: 0;
transform: scale(0);
}
label .cover-checkbox svg {
width: 13px;
height: 11px;
display: inline-block;
vertical-align: top;
fill: none;
margin: 5px 0 0 3px;
stroke: #fff;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 16px;
transition: stroke-dashoffset 0.4s ease var(--transition);
stroke-dashoffset: 16px;
}
label .info {
text-align: center;
margin-top: 0.2rem;
font-weight: 600;
font-size: 0.8rem;
}
</style>
</head>
<body>
<div class="cont-bg">
<div class="cont-title">Checkbox</div>
<div class="cont-main">
<div class="cont-checkbox">
<input type="checkbox" id="myCheckbox-1" />
<label for="myCheckbox-1">
<img
src="/sites/default/files/2026-01/2021-mazda-mx-5-miata-mmp-1-1593459650.avif"
draggable="false"
alt="mazda mx-5 miata"
/>
<span class="cover-checkbox">
<svg viewBox="0 0 12 10">
<polyline points="1.5 6 4.5 9 10.5 1"></polyline>
</svg>
</span>
<div class="info">Mazda MX-5 Miata</div>
</label>
</div>
<div class="cont-checkbox">
<input type="checkbox" id="myCheckbox-2" />
<label for="myCheckbox-2">
<img
src="/sites/default/files/2026-01/2020-chevrolet-corvette-c8-102-1571146873.avif"
draggable="false"
alt="chevrolet corvette"
/>
<span class="cover-checkbox">
<svg viewBox="0 0 12 10">
<polyline points="1.5 6 4.5 9 10.5 1"></polyline>
</svg>
</span>
<div class="info">Toyota Supra</div>
</label>
</div>
</div>
<!-- Измененеий нет, те же чекбокс, только изменен тег <input> под радиокнопки -->
<div class="cont-title">Radio</div>
<div class="cont-main">
<div class="cont-checkbox">
<input type="radio" name="myRadio" id="myRadio-1" />
<label for="myRadio-1">
<img
src="/sites/default/files/2026-01/2021-mazda-mx-5-miata-mmp-1-1593459650.avif"
draggable="false"
alt="mazda mx-5 miata"
/>
<span class="cover-checkbox">
<svg viewBox="0 0 12 10">
<polyline points="1.5 6 4.5 9 10.5 1"></polyline>
</svg>
</span>
<div class="info">Mazda MX-5 Miata</div>
</label>
</div>
<div class="cont-checkbox">
<input type="radio" name="myRadio" id="myRadio-2" />
<label for="myRadio-2">
<img
src="/sites/default/files/2026-01/2020-chevrolet-corvette-c8-102-1571146873.avif"
draggable="false"
alt="chevrolet corvette"
/>
<span class="cover-checkbox">
<svg viewBox="0 0 12 10">
<polyline points="1.5 6 4.5 9 10.5 1"></polyline>
</svg>
</span>
<div class="info">Toyota Supra</div>
</label>
</div>
</div>
</div>
</body>
</html>