CSS checkbox y radios personalizados

No hay diseñador web que acabe agotado de personalizar los formularios. Son complejos y llenos de sutilezas que debes conocer. Y por si no fuera poco hay lugares imposibles de tocar como los input radio y los input checkbox... ¡Hasta ahora!

En el siguiente tutorial voy a enseñar como realizar un checkbox y un radio personalizado para un formulario en HTML. Usando la metodología BEM para organizar los estilos en un formato de componente fácil de construir o modificiar. Todo ello sin preocuparnos de las limitaciones propias del CSS logrando adaptar nuestros diseños, y sin sacrificar la accesibilidad: el input seguirá siendo utilizable con el teclado y visible para los lectores de pantalla. Además incluiré un resumen en Sass para que sea fácil de copiar. Compatible con todos los navegadores más utilizados: Chrome, Firefox, Edge, Safari...

DEMO

Pulsa en cada elemento para ver su funcionamiento.

Radio

Checkbox

Ocultar no es destruir

Toda la técnica se apoya en esconder el input real y pintar un sustituto. La tentación clásica es display: none, y es una trampa: elimina el input del orden de tabulación y del árbol de accesibilidad. El resultado es que nadie puede marcarlo con Tab y Espacio, y un lector de pantalla ni siquiera sabe que existe. Si el checkbox es el típico "Acepto los términos y condiciones", habrás bloqueado el envío del formulario a cualquier persona que no use ratón.

La alternativa es ocultarlo solo visualmente: reducirlo a un píxel recortado. El input sigue existiendo, sigue recibiendo foco y Espacio lo marca; el label que lo envuelve se encarga de los clics de ratón.

.custom-radio-checkbox > .custom-radio-checkbox__input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

Solo falta un detalle: al navegar con teclado hay que ver dónde está el foco. Como el input es invisible, dibujamos el anillo sobre su sustituto.

.custom-radio-checkbox > .custom-radio-checkbox__input:focus-visible + .custom-radio-checkbox__show {
    outline: 2px solid #000;
    outline-offset: 2px;
}

Prueba la demo de arriba con el tabulador: puedes recorrer los controles, cambiar de radio con las flechas y marcar el checkbox con Espacio.

Radio

Estructuramos nuestro radio de forma natural. Pero debemos incluir un span que será el lugar donde mostraremos la imagen con el sustituto al radio.

<p>
    <label class="custom-radio-checkbox">
        <!-- Input oculto -->
        <input class="custom-radio-checkbox__input" type="radio" name="genero" value="mujer" checked>
        <!-- Imagen en sustitucion -->
        <span class="custom-radio-checkbox__show custom-radio-checkbox__show--radio"></span>
        <!-- Texto -->
        <span class="custom-radio-checkbox__text">Mujer</span>
    </label>
</p>
<p>
    <label class="custom-radio-checkbox">
        <!-- Input oculto -->
        <input class="custom-radio-checkbox__input" type="radio" name="genero" value="hombre">
        <!-- Imagen en sustitucion -->
        <span class="custom-radio-checkbox__show custom-radio-checkbox__show--radio"></span>
        <!-- Texto -->
        <span class="custom-radio-checkbox__text">Hombre</span>
    </label>
</p>

Ocultamos los radios con el truco accesible que acabamos de ver y en su lugar dejamos en los span una imagen de fondo que previamente hemos preparado.

/* Centra el input personalizado con el texto */
.custom-radio-checkbox {
    display: inline-flex;
    align-items: center;
    cursor: pointer;
    font-family: arial;
}

/* Modificador para dar la imagen de radio */
.custom-radio-checkbox__show--radio {
    background-image: url(radio-uncheck.png);
}

/* Oculta el input original sin sacarlo del teclado ni del lector de pantalla */
.custom-radio-checkbox > .custom-radio-checkbox__input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* Radio personalizado usando <span> */
.custom-radio-checkbox > .custom-radio-checkbox__show {
    display: inline-block;
    width: 15px;
    height: 15px;
    margin-right: .5rem;
    background-size: cover;
}

/* Anillo de foco sobre el sustituto al navegar con teclado */
.custom-radio-checkbox > .custom-radio-checkbox__input:focus-visible + .custom-radio-checkbox__show {
    outline: 2px solid #000;
    outline-offset: 2px;
}

/* Cambia el radio personalizado cuando es pulsado */
.custom-radio-checkbox > .custom-radio-checkbox__input:checked + .custom-radio-checkbox__show--radio {
    background-image: url(radio-check.png);
}

La imagen sin pulsar sería la siguiente.

Radio sin pulsar

Y pulsado sería así.

Radio pulsado

Checkbox

Se trabaja de la misma forma. Primero declaramos nuestro input dentro de un label, sin olvidar un span vacío que será el futuro espacio para la imagen.

<label class="custom-radio-checkbox">
    <!-- Input oculto -->
    <input class="custom-radio-checkbox__input" type="checkbox" name="genero" value="hombre">
    <!-- Imagen en sustitucion -->
    <span class="custom-radio-checkbox__show custom-radio-checkbox__show--checkbox"></span>
    <!-- Texto -->
    <span class="custom-radio-checkbox__text">Acepto las condiciones de uso y privacidad</span>
</label>

Mostramos una imagen de fondo dentro del span y ocultamos el checkbox original, de nuevo sin display: none para que Espacio siga funcionando.

/* Centra el input personalizado con el texto */
.custom-radio-checkbox {
    display: inline-flex;
    align-items: center;
    cursor: pointer;
    font-family: arial;
}

/* Modificador para dar la imagen de checkbox */
.custom-radio-checkbox__show--checkbox {
    background-image: url(checkbox-uncheck.png);
}

/* Oculta el input original sin sacarlo del teclado ni del lector de pantalla */
.custom-radio-checkbox > .custom-radio-checkbox__input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* Radio personalizado usando <span> */
.custom-radio-checkbox > .custom-radio-checkbox__show {
    display: inline-block;
    width: 15px;
    height: 15px;
    margin-right: .5rem;
    background-size: cover;
}

/* Anillo de foco sobre el sustituto al navegar con teclado */
.custom-radio-checkbox > .custom-radio-checkbox__input:focus-visible + .custom-radio-checkbox__show {
    outline: 2px solid #000;
    outline-offset: 2px;
}

/* Cambia el checkbox personalizado cuando es pulsado */
.custom-radio-checkbox > .custom-radio-checkbox__input:checked + .custom-radio-checkbox__show--checkbox {
    background-image: url(checkbox-check.png);
}

La imagen sin pulsar sería la siguiente.

Checkbox sin pulsar

Y pulsado sería así.

Checkbox pulsado

Todo cohesionado en un componente

HTML

<!-- Radios -->
<p>
    <label class="custom-radio-checkbox">
        <!-- Input oculto -->
        <input class="custom-radio-checkbox__input" type="radio" name="genero" value="mujer" checked>
        <!-- Imagen en sustitucion -->
        <span class="custom-radio-checkbox__show custom-radio-checkbox__show--radio"></span>
        <!-- Texto -->
        <span class="custom-radio-checkbox__text">Mujer</span>
    </label>
</p>
<p>
    <label class="custom-radio-checkbox">
        <!-- Input oculto -->
        <input class="custom-radio-checkbox__input" type="radio" name="genero" value="hombre">
        <!-- Imagen en sustitucion -->
        <span class="custom-radio-checkbox__show custom-radio-checkbox__show--radio"></span>
        <!-- Texto -->
        <span class="custom-radio-checkbox__text">Hombre</span>
    </label>
</p>
<!-- End Radios -->

<!-- Checkbox -->
<p>
    <label class="custom-radio-checkbox">
        <!-- Input oculto -->
        <input class="custom-radio-checkbox__input" type="checkbox" name="genero" value="hombre">
        <!-- Imagen en sustitucion -->
        <span class="custom-radio-checkbox__show custom-radio-checkbox__show--checkbox"></span>
        <!-- Texto -->
        <span class="custom-radio-checkbox__text">Acepto las condiciones de uso y privacidad</span>
    </label>
</p>
<!-- End Checkbox -->

CSS

/* Centra el input personalizado con el texto */
.custom-radio-checkbox {
    display: inline-flex;
    align-items: center;
    cursor: pointer;
    font-family: arial;
}

/* Modificador para dar la imagen de checkbox */
.custom-radio-checkbox__show--checkbox {
    background-image: url(checkbox-uncheck.png);
}

/* Modificador para dar la imagen de radio */
.custom-radio-checkbox__show--radio {
    background-image: url(radio-uncheck.png);
}

/* Oculta el input original sin sacarlo del teclado ni del lector de pantalla */
.custom-radio-checkbox > .custom-radio-checkbox__input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* Radio personalizado usando <span> */
.custom-radio-checkbox > .custom-radio-checkbox__show {
    display: inline-block;
    width: 15px;
    height: 15px;
    margin-right: .5rem;
    background-size: cover;
}

/* Anillo de foco sobre el sustituto al navegar con teclado */
.custom-radio-checkbox > .custom-radio-checkbox__input:focus-visible + .custom-radio-checkbox__show {
    outline: 2px solid #000;
    outline-offset: 2px;
}

/* Cambia el checkbox personalizado cuando es pulsado */
.custom-radio-checkbox > .custom-radio-checkbox__input:checked + .custom-radio-checkbox__show--checkbox {
    background-image: url(checkbox-check.png);
}

/* Cambia el radio personalizado cuando es pulsado */
.custom-radio-checkbox > .custom-radio-checkbox__input:checked + .custom-radio-checkbox__show--radio {
    background-image: url(radio-check.png);
}

Sass

.custom-radio-checkbox
  display: inline-flex
  align-items: center
  cursor: pointer
  font-family: arial
  &__show--checkbox
    background-image: url(checkbox-uncheck.png)
  &__show--radio
    background-image: url(radio-uncheck.png)
  &__input
    position: absolute
    width: 1px
    height: 1px
    overflow: hidden
    clip: rect(0 0 0 0)
    white-space: nowrap
  &__show
    display: inline-block
    width: 15px
    height: 15px
    margin-right: .5rem
    background-size: cover
  &__input:focus-visible + &__show
    outline: 2px solid #000
    outline-offset: 2px
  &__input:checked + &__show--checkbox
    background-image: url(checkbox-check.png)
  &__input:checked + &__show--radio
    background-image: url(radio-check.png)

Help me keep writing

Every coffee gives me a push toward the next article.

Comments

There are no comments yet.

You may also like