Intégration web angularforall.com

- Styliser une case à cocher en HTML et CSS pur

Checkbox Html Css Design Formulaires Appearance-None Pseudo-Elements Css-Pur Front-End Ux-Formulaires Transitions Icones-Svg Css-Moderne Integration-Web
Styliser une case à cocher en HTML et CSS pur

Personnalisez vos checkboxes en HTML et CSS : appearance:none, pseudo-éléments, transitions, icônes SVG pour un design moderne et accessible sans JavaScript.

Pourquoi personnaliser les checkboxes

Par défaut, les cases à cocher sont rendues par le navigateur système. Chrome, Firefox et Safari ont des styles visuels différents, ce qui crée une incohérence de rendu. En CSS moderne, on peut les contrôler entièrement sans JavaScript.

ApprocheTechniqueJavaScriptAccessibilité
Apparence nativeAucuneNon Native
CSS appearance: noneCSS purNon Préservée
Label + input hiddenCSS purNon Nécessite aria
Composant JS customJS/FrameworkOui À gérer manuellement
Bootstrap form-checkCSS + classesOptionnel Bien documenté
Bonne pratique : Utilisez appearance: none avec <input type="checkbox"> natif. L'accessibilité clavier et lecteur d'écran est préservée automatiquement.

appearance: none — supprimer le style natif

La propriété CSS appearance: none (standardisée en CSS Basic UI Level 4) supprime le rendu système d'un élément de formulaire. L'élément reste fonctionnel mais devient un conteneur vierge stylisable.

/* Réinitialisation complète du rendu système */
input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;   /* Safari */
    -moz-appearance: none;      /* Firefox (legacy) */

    /* Dimensions explicites obligatoires après reset */
    width: 20px;
    height: 20px;
    min-width: 20px;             /* évite la compression dans les flex layouts */

    border: 2px solid #ced4da;
    border-radius: 4px;
    background-color: #fff;
    cursor: pointer;

    /* Important : vertical-align pour l'alignement inline */
    vertical-align: middle;
    position: relative;          /* nécessaire pour ::before/::after */

    /* Transition globale */
    transition: border-color 0.15s ease, background-color 0.15s ease,
                box-shadow 0.15s ease;
}
Compatibilité : appearance: none (sans préfixe) est supporté dans Chrome 84+, Firefox 80+, Safari 15.4+. Les préfixes vendor couvrent les versions plus anciennes. Support global : 97%+ (caniuse.com).

Stylisation de base avec SVG checkmark

La technique la plus propre pour afficher un checkmark est d'injecter un SVG en data URI via background-image. Pas d'image externe, pas de font-icon, encodage UTF-8 compatible tous navigateurs.

/* Système complet de checkbox stylisée */
.checkbox-custom {
    appearance: none;
    -webkit-appearance: none;
    width: 20px;
    height: 20px;
    border: 2px solid #6c757d;
    border-radius: 4px;
    background-color: #fff;
    cursor: pointer;
    position: relative;
    vertical-align: middle;
    transition: all 0.15s ease-in-out;
}

/* État cochée */
.checkbox-custom:checked {
    background-color: #0d6efd;
    border-color: #0d6efd;

    /* SVG checkmark en data URI — encodage minimal valide dans les CSS */
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 14px;
}

/* État focus */
.checkbox-custom:focus {
    outline: 0;
    box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
}

/* État hover */
.checkbox-custom:hover:not(:disabled) {
    border-color: #0d6efd;
}

/* État désactivée */
.checkbox-custom:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    background-color: #e9ecef;
}
<!-- HTML d'utilisation avec label associé -->
<div class="d-flex align-items-center gap-2 mb-2">
    <input type="checkbox" id="check1" class="checkbox-custom">
    <label for="check1">Option 1 — Recevoir les notifications</label>
</div>

<div class="d-flex align-items-center gap-2 mb-2">
    <input type="checkbox" id="check2" class="checkbox-custom" checked>
    <label for="check2">Option 2 — pré-cochée</label>
</div>

<div class="d-flex align-items-center gap-2">
    <input type="checkbox" id="check3" class="checkbox-custom" disabled>
    <label for="check3" class="text-muted">Option 3 — désactivée</label>
</div>

Variantes de couleurs

/* Variante success */
.checkbox-success:checked {
    background-color: #198754;
    border-color: #198754;
}

.checkbox-success:focus {
    box-shadow: 0 0 0 3px rgba(25, 135, 84, 0.25);
}

/* Variante danger */
.checkbox-danger:checked {
    background-color: #dc3545;
    border-color: #dc3545;
}

/* Variante avec dégradé */
.checkbox-gradient:checked {
    background-color: transparent;
    border-color: #764ba2;
    background-image:
        url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"),
        linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    background-size: 14px, cover;
    background-position: center, center;
    background-repeat: no-repeat;
}

États interactifs et transitions

Une checkbox de qualité répond visuellement à chaque interaction utilisateur : hover, focus (navigation clavier), checked, disabled. Les transitions rendent l'expérience fluide.

/* Système d'états complet */
.checkbox-interactive {
    appearance: none;
    -webkit-appearance: none;
    width: 22px;
    height: 22px;
    border: 2px solid #adb5bd;
    border-radius: 5px;
    background-color: #fff;
    cursor: pointer;
    vertical-align: middle;
    transition:
        background-color 0.15s ease,
        border-color 0.15s ease,
        box-shadow 0.15s ease,
        transform 0.1s ease;
}

/* Hover — survol de la souris */
.checkbox-interactive:hover:not(:disabled):not(:checked) {
    border-color: #0d6efd;
    background-color: rgba(13, 110, 253, 0.05);
}

/* Focus — navigation clavier (Tab) */
.checkbox-interactive:focus-visible {
    outline: 2px solid #0d6efd;
    outline-offset: 3px;
}

/* Focus + checked */
.checkbox-interactive:checked:focus-visible {
    outline-color: #0a58ca;
}

/* Checked — case cochée */
.checkbox-interactive:checked {
    background-color: #0d6efd;
    border-color: #0d6efd;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 14px;
    /* Micro-animation de confirmation */
    transform: scale(1.05);
}

/* Retour à taille normale après animation */
.checkbox-interactive:checked:not(:active) {
    transform: scale(1);
}

/* Active — clic en cours */
.checkbox-interactive:active:not(:disabled) {
    transform: scale(0.9);
    border-color: #0a58ca;
}

/* Disabled — désactivée */
.checkbox-interactive:disabled {
    opacity: 0.45;
    cursor: not-allowed;
    background-color: #f8f9fa;
    border-color: #dee2e6;
    transform: none;
}

/* Checked + disabled */
.checkbox-interactive:checked:disabled {
    background-color: #6ea8fe;
    border-color: #6ea8fe;
}

État indéterminé (tri-state)

L'état indeterminate d'une checkbox est utilisé dans les sélecteurs en cascade (type "sélectionner tout"). Il s'active uniquement via JavaScript ou via l'attribut HTML5 (pas en CSS pur). Il est stylisable via la pseudo-classe :indeterminate.

<!-- HTML -->
<input type="checkbox" id="select-all" class="checkbox-tri">
<label for="select-all">Sélectionner tout</label>

<input type="checkbox" id="item-1" class="checkbox-tri" checked>
<label for="item-1">Item 1</label>

<input type="checkbox" id="item-2" class="checkbox-tri">
<label for="item-2">Item 2</label>
/* CSS — état indéterminé */
.checkbox-tri:indeterminate {
    background-color: #0d6efd;
    border-color: #0d6efd;
    /* Tiret horizontal au lieu du checkmark */
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 14px;
}
// JavaScript — gestion du tri-state
const selectAll = document.getElementById('select-all');
const items = document.querySelectorAll('.item-checkbox');

// Mettre à jour le "sélectionner tout" quand un item change
items.forEach(item => {
    item.addEventListener('change', updateSelectAll);
});

function updateSelectAll() {
    const checkedCount = [...items].filter(i => i.checked).length;

    if (checkedCount === 0) {
        selectAll.checked = false;
        selectAll.indeterminate = false;
    } else if (checkedCount === items.length) {
        selectAll.checked = true;
        selectAll.indeterminate = false;
    } else {
        selectAll.checked = false;
        selectAll.indeterminate = true;  // Activer l'état indéterminé
    }
}

// Sélectionner/désélectionner tout
selectAll.addEventListener('change', () => {
    items.forEach(item => { item.checked = selectAll.checked; });
});

Toggle switch (style iOS)

Le toggle switch est une checkbox stylisée pour ressembler à un interrupteur mobile. Deux approches : via appearance: none sur l'input, ou via le pattern label + input hidden.

Approche avec appearance: none (recommandée)

/* Toggle switch CSS pur */
.toggle-switch {
    appearance: none;
    -webkit-appearance: none;
    width: 52px;
    height: 28px;
    border-radius: 28px;
    background-color: #ced4da;
    cursor: pointer;
    position: relative;
    vertical-align: middle;
    transition: background-color 0.2s ease;
}

/* Le "bouton" circulaire du toggle */
.toggle-switch::before {
    content: '';
    position: absolute;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background-color: #fff;
    top: 3px;
    left: 3px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    transition: transform 0.2s ease;
}

/* État activé */
.toggle-switch:checked {
    background-color: #0d6efd;
}

/* Déplacer le bouton vers la droite */
.toggle-switch:checked::before {
    transform: translateX(24px);
}

/* Focus accessible */
.toggle-switch:focus-visible {
    outline: 2px solid #0d6efd;
    outline-offset: 3px;
}

/* Désactivé */
.toggle-switch:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Variante de taille large */
.toggle-switch--lg {
    width: 64px;
    height: 36px;
    border-radius: 36px;
}

.toggle-switch--lg::before {
    width: 28px;
    height: 28px;
    top: 4px;
    left: 4px;
}

.toggle-switch--lg:checked::before {
    transform: translateX(28px);
}
<!-- Toggle avec label et état textuel -->
<div class="d-flex align-items-center gap-3">
    <input type="checkbox" id="toggle-notif" class="toggle-switch" role="switch"
           aria-checked="false">
    <label for="toggle-notif" class="mb-0">
        Notifications par email
    </label>
</div>

<!-- Avec indicateur On/Off via CSS -->
<div class="toggle-wrapper d-flex align-items-center gap-2">
    <span class="text-muted small">Off</span>
    <input type="checkbox" id="toggle-theme" class="toggle-switch">
    <span class="text-muted small">On</span>
</div>
ARIA : Ajoutez role="switch" et aria-checked sur le toggle pour les lecteurs d'écran. Mettez à jour aria-checked via JavaScript au changement d'état.

Boutons radio personnalisés

La même technique s'applique aux <input type="radio">. La différence visuelle clé : les radios utilisent un border-radius: 50% (cercle) et un point central au lieu d'un checkmark.

/* Bouton radio personnalisé */
.radio-custom {
    appearance: none;
    -webkit-appearance: none;
    width: 20px;
    height: 20px;
    border: 2px solid #ced4da;
    border-radius: 50%;           /* Cercle — différence clé avec la checkbox */
    background-color: #fff;
    cursor: pointer;
    vertical-align: middle;
    position: relative;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Point central quand sélectionné */
.radio-custom::after {
    content: '';
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: #0d6efd;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0);
    transition: transform 0.1s ease;
}

/* État sélectionné */
.radio-custom:checked {
    border-color: #0d6efd;
}

.radio-custom:checked::after {
    transform: translate(-50%, -50%) scale(1);
}

/* Focus */
.radio-custom:focus-visible {
    outline: 2px solid #0d6efd;
    outline-offset: 3px;
}

/* Hover */
.radio-custom:hover:not(:disabled) {
    border-color: #0d6efd;
}

/* Disabled */
.radio-custom:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

Radio buttons en mode "cards"

<!-- Radio buttons stylisés comme des cards cliquables -->
<div class="radio-card-group">
    <label class="radio-card">
        <input type="radio" name="plan" value="free" class="radio-card-input">
        <div class="radio-card-body">
            <h5>Gratuit</h5>
            <p>0€/mois — jusqu'à 3 projets</p>
        </div>
    </label>
    <label class="radio-card">
        <input type="radio" name="plan" value="pro" class="radio-card-input">
        <div class="radio-card-body">
            <h5>Pro</h5>
            <p>29€/mois — projets illimités</p>
        </div>
    </label>
</div>
/* Radio card styles */
.radio-card-group { display: flex; gap: 16px; flex-wrap: wrap; }

.radio-card {
    flex: 1;
    min-width: 140px;
    border: 2px solid #dee2e6;
    border-radius: 8px;
    padding: 16px;
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.radio-card:hover { border-color: #0d6efd; }

/* Cacher l'input natif mais le garder accessible */
.radio-card-input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

/* Card sélectionnée */
.radio-card:has(.radio-card-input:checked) {
    border-color: #0d6efd;
    background-color: rgba(13, 110, 253, 0.05);
    box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
}

Thématisation avec CSS custom properties

En centralisant les couleurs dans des variables CSS, vous pouvez changer le thème de toutes vos checkboxes en modifiant un seul endroit.

/* Définition du système de tokens de couleur */
:root {
    --checkbox-border: #ced4da;
    --checkbox-border-focus: #0d6efd;
    --checkbox-bg: #fff;
    --checkbox-bg-checked: #0d6efd;
    --checkbox-border-checked: #0d6efd;
    --checkbox-focus-ring: rgba(13, 110, 253, 0.25);
    --checkbox-disabled-opacity: 0.45;
    --checkbox-size: 20px;
    --checkbox-radius: 4px;
}

/* Thème "success" */
.theme-success {
    --checkbox-border-focus: #198754;
    --checkbox-bg-checked: #198754;
    --checkbox-border-checked: #198754;
    --checkbox-focus-ring: rgba(25, 135, 84, 0.25);
}

/* Thème "danger" */
.theme-danger {
    --checkbox-border-focus: #dc3545;
    --checkbox-bg-checked: #dc3545;
    --checkbox-border-checked: #dc3545;
    --checkbox-focus-ring: rgba(220, 53, 69, 0.25);
}

/* Application des tokens */
.checkbox-themed {
    appearance: none;
    -webkit-appearance: none;
    width: var(--checkbox-size);
    height: var(--checkbox-size);
    border: 2px solid var(--checkbox-border);
    border-radius: var(--checkbox-radius);
    background-color: var(--checkbox-bg);
    cursor: pointer;
    vertical-align: middle;
    transition: all 0.15s ease;
}

.checkbox-themed:checked {
    background-color: var(--checkbox-bg-checked);
    border-color: var(--checkbox-border-checked);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 14px;
}

.checkbox-themed:focus-visible {
    outline: 0;
    box-shadow: 0 0 0 3px var(--checkbox-focus-ring);
}

Support du dark mode

/* Dark mode via media query */
@media (prefers-color-scheme: dark) {
    :root {
        --checkbox-border: #6c757d;
        --checkbox-bg: #343a40;
        --checkbox-bg-checked: #6ea8fe;
        --checkbox-border-checked: #6ea8fe;
        --checkbox-border-focus: #6ea8fe;
        --checkbox-focus-ring: rgba(110, 168, 254, 0.25);
    }
}

/* Dark mode via data-attribute (Bootstrap 5 / toggleable) */
[data-bs-theme="dark"],
[data-theme="dark"] {
    --checkbox-border: #6c757d;
    --checkbox-bg: #343a40;
    --checkbox-bg-checked: #6ea8fe;
    --checkbox-border-checked: #6ea8fe;
    --checkbox-border-focus: #6ea8fe;
    --checkbox-focus-ring: rgba(110, 168, 254, 0.25);
}

/* Ajustement du texte du label */
[data-theme="dark"] label {
    color: #dee2e6;
}

/* Toggle switch dark mode */
@media (prefers-color-scheme: dark) {
    .toggle-switch {
        background-color: #495057;
    }

    .toggle-switch::before {
        background-color: #dee2e6;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
    }

    .toggle-switch:checked {
        background-color: #6ea8fe;
    }
}

Checkboxes Bootstrap 4/5

Bootstrap propose des variantes de checkboxes intégrées qui utilisent elles-mêmes appearance: none sous le capot.

Bootstrap 5 — form-check

<!-- Checkbox Bootstrap 5 standard -->
<div class="form-check">
    <input class="form-check-input" type="checkbox" id="check-bs5" value="1">
    <label class="form-check-label" for="check-bs5">
        Checkbox standard Bootstrap 5
    </label>
</div>

<!-- Checkbox avec switch Bootstrap 5 -->
<div class="form-check form-switch">
    <input class="form-check-input" type="checkbox" role="switch" id="switch-bs5">
    <label class="form-check-label" for="switch-bs5">
        Mode sombre
    </label>
</div>

<!-- Checkbox inline -->
<div class="form-check form-check-inline">
    <input class="form-check-input" type="checkbox" id="inline1">
    <label class="form-check-label" for="inline1">Option A</label>
</div>
<div class="form-check form-check-inline">
    <input class="form-check-input" type="checkbox" id="inline2" checked>
    <label class="form-check-label" for="inline2">Option B</label>
</div>

<!-- Personnaliser les couleurs Bootstrap 5 via SCSS -->
/* Surcharger les variables Bootstrap 5 dans votre SCSS */
$form-check-input-checked-bg-color:  #198754; /* success vert */
$form-check-input-checked-border-color: #198754;
$form-check-input-focus-box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, .25);

/* Ou via CSS custom properties (pas de SCSS requis) */
.form-check-input:checked {
    background-color: #198754;
    border-color: #198754;
}

.form-check-input:focus {
    box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, .25);
}

Validation de formulaire CSS

Les pseudo-classes :valid et :invalid permettent d'afficher des états de validation sans JavaScript, en utilisant l'attribut required.

<!-- Checkbox required — invalide tant que non cochée -->
<form novalidate id="consent-form">
    <div class="mb-3">
        <input type="checkbox" id="cgu" class="checkbox-validated" required>
        <label for="cgu">J'accepte les CGU <span class="text-danger">*</span></label>
        <div class="validation-msg"></div>
    </div>
    <button type="submit" class="btn btn-primary">Valider</button>
</form>
/* Styles de validation */
.was-validated .checkbox-validated:valid {
    border-color: #198754;
}

.was-validated .checkbox-validated:invalid {
    border-color: #dc3545;
    box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
}

/* Message d'erreur visible quand invalide */
.was-validated .checkbox-validated:invalid ~ .validation-msg::after {
    content: "Vous devez accepter les CGU pour continuer.";
    display: block;
    color: #dc3545;
    font-size: 0.875rem;
    margin-top: 4px;
}
// JavaScript — ajouter was-validated à la soumission
document.getElementById('consent-form').addEventListener('submit', function(e) {
    if (!this.checkValidity()) {
        e.preventDefault();
        this.classList.add('was-validated');
    }
});

Accessibilité et bonnes pratiques

  • Utilisez toujours un <label> associé via for="id" — la zone de clic est élargie au texte
  • Conservez <input type="checkbox"> natif avec appearance: none — la navigation clavier Tab/Space est native
  • Stylisez :focus-visible (pas :focus) — visible seulement pour le clavier, pas la souris
  • Garantissez un ratio de contraste 4.5:1 entre la couleur d'état et l'arrière-plan
  • Ajoutez role="switch" + aria-checked sur les toggles pour les lecteurs d'écran
  • Zone de clic minimum : 44×44px sur mobile (padding étendu ou min-width/min-height)
  • Ne cachez pas l'input avec display: none ou visibility: hidden — préférez opacity: 0; position: absolute si besoin
  • Ne vous fiez pas à la couleur seule pour indiquer un état — ajoutez un checkmark ou texte
Test rapide : Naviguez dans votre formulaire avec Tab seulement. Si vous ne voyez pas les cases à cocher sélectionnées, votre style :focus est invisible — correction urgente WCAG 2.1 AA.

Conclusion

La personnalisation des cases à cocher HTML/CSS est une compétence essentielle du développeur front-end. La technique appearance: none combinée au SVG data URI offre un contrôle total sans JavaScript, tout en préservant l'accessibilité native.

Pour les projets Bootstrap, le composant form-check (Bootstrap 5) avec surcharge de variables SCSS est la solution la plus maintenable. Pour des designs vraiment custom, le système de CSS custom properties permet une thématisation cohérente et un support dark mode élégant.

Partager