Перейти к основному содержанию

Кастомизация чекбоксов и радиокнопок через CSS и изображения

Оригинал примера: https://stackoverflow.com/questions/3772273/pure-css-checkbox-image-rep…

  1. Конструкция для чекбоксов и радиокнопок схожи, за исключением отображения самих элементов формы.
    По этой причине разбор идет по одному элементу (чекбоксы). Второй (радиокнопки) добавим в самом конце, копированием 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" />
    
  2. Стоит заранее приготовить изображения или привести их к одинаковой ширине или пропорции высота/ширина. 

    Указанный источник медиа отсутствует и требует повторной вставки.
    Указанный источник медиа отсутствует и требует повторной вставки.

 

Шаг 1. Создаем форму с чекбоксами

  1. Создаем форму с чекбоксами
  2. Под каждым чекбоксом размещаем <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. Добавляем стили.

  1. Скрываем чекбоксы
  2. Выравниваем картинки, заголовок с помощью 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).
    1. Картинка встает на линию, на которой пишутся буквы.
    2. Ниже этой линии браузер резервирует несколько пикселей для «хвостиков» букв (таких как «ц», «щ», «y», «g»).
    3. Даже если текста нет, этот пустой зазор (обычно 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
     
    1. В графике часто приходится рисовать пунктирные линии (чертежи, карты, схемы). Для этих целей ввели свойство stroke-dasharray.
      Буквально значение stroke-dasharray: 16px; говорит следующее: рисуй линию длиной 16, потом пропуск 16, повторить.
      Пример: stroke-dasharray: 10px 5px 2px 5px;  — штрих 10 → пробел 5 → штрих 2 → пробел 5 → и повтор...
       
    2. 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>
    3. значения единиц измерения stroke-dasharray и stroke-dashoffset часто пишут в px , но можно и не указывать, т.к. в svg расчет идет в единицах svg
       
    4. 3. Из  пунктов 1 и 2: если длина линии меньше stroke-dasharray и stroke-dasharray = stroke-dashoffset, то элемент будет невидим!
       
    5. С помощью изменения свойства 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>

 

Tags