TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸

Formularelemente mit CSS anpassen (Checkboxes und Radios)

August 12, 2020

Standardmäßige Formularelemente wie Checkboxen (<input type="checkbox">) und Radio-Buttons (<input type="radio">) sind berüchtigt dafür, dass sie sich direkt über CSS kaum anpassen lassen. Ein Versuch, ihren Hintergrund oder Rahmen direkt zu stylen, schlägt in den meisten Browsern fehl.

Diese Anleitung beschreibt zwei etablierte Methoden, um vollkommen freie, maßgeschneiderte Designs für diese Elemente zu erstellen – ohne dabei die Barrierefreiheit (Tastatur-Navigation mit der Tab-Taste) zu beeinträchtigen.


1. Die HTML-Strukturen

Es gibt zwei gängige Arten, Checkboxen und Labels im HTML-Code zu strukturieren. Die Wahl beeinflusst die CSS-Selektoren.

Struktur A: Label und Input nebeneinander (Empfohlen für Styling)

Die Elemente sind Geschwister. Sie müssen über das id- und for-Attribut miteinander verknüpft werden.

code
<input id="pizza" type="checkbox" className="custom-checkbox" />
<label for="pizza">Ganze Pizza?</label>

Struktur B: Input innerhalb des Labels (Einfacher zu schreiben)

Die Elemente sind automatisch verknüpft. Das Stylen ist jedoch etwas unelastischer, da das Input ein Kind des Labels ist.

code
<label>
  <input type="checkbox" className="custom-checkbox" />
  Ganze Pizza?
</label>

2. Methode 1: Die moderne Lösung mit appearance: none

Die modernste und eleganteste Methode verwendet die Eigenschaft appearance: none. Sie weist den Browser an, das standardmäßige, plattformspezifische Design des Elements komplett zu entfernen, sodass es sich wie ein normales, leeres Block-Element verhält, das vollständig mit CSS gestaltet werden kann.

Diese Methode ist sauber, da das originale input-Element erhalten bleibt und seine Tastatur-Fokussierbarkeit (:focus) nativ funktioniert.

code
/* Deaktiviert das Standard-Design */
input[type="checkbox"].custom-style,
input[type="radio"].custom-style {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;

  /* Eigene Box-Größe und Design definieren */
  display: inline-block;
  width: 1.25em;
  height: 1.25em;
  vertical-align: middle;
  border: 2px solid #007bff;
  background-color: white;
  cursor: pointer;
  margin-right: 0.5em;
  position: relative;
  transition: background-color 0.2s, border-color 0.2s;
}

/* Kreisrundes Design für Radio-Buttons */
input[type="radio"].custom-style {
  border-radius: 50%;
}

/* Eckiges Design für Checkboxen */
input[type="checkbox"].custom-style {
  border-radius: 4px;
}

/* Zustand bei Fokussierung (Tastatur-Navigation) - ESSENTIELL FÜR A11Y! */
input[type="checkbox"].custom-style:focus,
input[type="radio"].custom-style:focus {
  outline: 3px solid rgba(0, 123, 255, 0.25);
  outline-offset: 1px;
}

/* Zustand im ausgewählten Zustand (Checked) */
input[type="checkbox"].custom-style:checked,
input[type="radio"].custom-style:checked {
  background-color: #007bff;
}

/* Ein Häkchen oder Point im ausgewählten Zustand hinzufügen (über Pseudo-Elemente) */
input[type="checkbox"].custom-style:checked::after {
  content: "✔";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: white;
  font-size: 0.85em;
}

input[type="radio"].custom-style:checked::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background-color: white;
}

3. Methode 2: Die klassische Lösung mit verstecktem Input und dem +-Nachbar-Selektor

Wenn Sie ältere Browser unterstützen müssen oder komplexe grafische Effekte (wie eigene SVG-Grafiken für das Häkchen) benötigen, ist die klassische Methode über den Nachbar-Selektor (+) die beste Wahl.

Hierbei wird das originale input visuell versteckt (aber nicht mit display: none oder visibility: hidden ausgeblendet, da es sonst nicht mehr mit der Tab-Taste fokussiert werden kann!). Das Design wird vollständig über das nachfolgende label (oder ein darin verschachteltes Hilfselement) gesteuert.

HTML (Struktur A erforderlich)

code
<div className="form-group">
  <input id="beer" type="checkbox" className="visually-hidden" />
  <label for="beer" className="custom-label">Bier dazu?</label>
</div>

CSS / SCSS

code
/* 
  Visuell verstecken, aber für Screenreader und Tastatur-Fokus erhalten!
  Besser als 'display: none'
*/
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

/* Das benutzerdefinierte Design auf dem Label aufbauen */
.custom-label {
  position: relative;
  padding-left: 2rem;
  cursor: pointer;
  user-select: none;
}

/* Die leere Box vor dem Label */
.custom-label::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 1.25em;
  height: 1.25em;
  border: 2px solid #ccc;
  background-color: white;
  border-radius: 4px;
  transition: border-color 0.2s, background-color 0.2s;
}

/* Das Häkchen (unsichtbar im Standardzustand) */
.custom-label::after {
  content: "✔";
  position: absolute;
  left: 0.35em;
  top: 50%;
  transform: translateY(-50%) scale(0);
  color: white;
  font-size: 0.95em;
  transition: transform 0.15s ease-out;
}

/* WENN der unsichtbare Input 'checked' ist, ändere das Design des Nachbar-Labels */
.visually-hidden:checked + .custom-label::before {
  background-color: #28a745;
  border-color: #28a745;
}

.visually-hidden:checked + .custom-label::after {
  transform: translateY(-50%) scale(1); /* Zeigt das Häkchen an */
}

/* WENN der unsichtbare Input den Tastatur-Fokus erhält, zeige den Rahmen auf dem Label */
.visually-hidden:focus + .custom-label::before {
  outline: 3px solid rgba(40, 167, 69, 0.25);
  outline-offset: 1px;
}

Fazit

  • Die appearance: none-Methode ist der modernste, sauberste und am einfachsten zu wartende Ansatz. Sie erfordert weniger HTML-Markup und funktioniert nativ barrierefrei.
  • Die Kombination aus visually-hidden und + label::before ist der klassische Weg, der maximale Gestaltungsfreiheit (auch mit komplexen SVGs) bietet und in jedem Browser seit dem Internet Explorer 9 funktioniert.

Achten Sie bei beiden Methoden penibel darauf, den :focus-Zustand visuell ansprechend zu stylen, um Benutzern, die auf Tastatur-Navigation angewiesen sind, eine barrierefreie Nutzung zu ermöglichen.