Stefani Gerber Senior Software Engineer

Gestylte Checkboxen und Radio-Buttons mit Angular

May 18, 2016 12:43:39 PM

Das Aussehen von Formularelementen wie Checkboxen, Radio-Buttons oder Dropdowns lassen sich nicht so einfach verändern wie das von anderen HTML-Elementen. Es gibt diverse JavaScript-Bibliotheken die Abhilfe bieten, oft entstehen dadurch aber neue Probleme:

  • die Elemente sind nicht mehr tastaturbedienbar und somit ist die Webseite nicht mehr barrierefrei
  • auf gewissen Geräten (z.B. auf Touch-Devices) treten Bugs auf
  • integriert sich schlecht in andere JavaScript-Frameworks wie z.B. ein Data-Binding mit Angular

Es gibt diverse Ansätze, Checkboxen und Radio-Buttons nur mit CSS (also ohne JavaScript) zu stylen. Am Beispiel von Roger Johansson's Vorschlag zeige ich nachfolgend, wie sich das mit Angular kombinieren lässt. Der Ansatz macht sich zu Nutze, dass die Interaktion mit einem <label>-Element an das verbundene Input-Feld delegiert wird. Klickt der Benutzer auf das Label einer Checkbox wird diese angehakt. Für das Styling ist zusätzliches Markup nötig

  • ein Element, um das Bild der Checkbox anzuzeigen
  • ein Wrapper, der das Bild und das Input-Feld umgibt
<span class="wrapper">
<input type="checkbox"/>
<i class="icon"></i>
</span>

Dieses Markup erzeugen wir mit einer Angular-Direktive:

app.directive("styledinput", function() {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
elem.wrap('<span class="styledinput-wrapper"></span>');
elem.after('<i></i>');
}
};
});

Vorteile:

  • im Template selber schreiben wir nur das semantisch relevante Markup
    <input type="checkbox" styledinput />
  • benötigen wir später mal eine andere Markup-Struktur müssen wir dies nur in der Direktive anpassen, nicht an allen verwendeten Stellen.

Erweiterungen

Obige Lösung geht davon aus, dass die Input-Felder immer innerhalb eines Label-Tags verwendet werden.

<label><input type="checkbox" styledinput /> Bedinungen akzeptieren</label> 

Die Direktive kann einfach erweitert werden, um auch mit Markup umgehen zu können, das sich nicht an diese Konvention hält. Z.B. so

link: function (scope, elem, attrs) {
var parentTag = elem.parent()[0].tagName.toLowerCase();
var wrapperTag = 'label';
if (parentTag === 'label') {
wrapperTag = 'span';
}
elem.wrap('<' + wrapperTag + ' class="styledinput-wrapper"></' + wrapperTag + '>');
elem.after('<i></i>');
}

Beispielcode

Wie all das zusammenspielt ist in https://jsfiddle.net/stefani_gerber/8mpz1asL/ zu sehen.