Maak een schakelaar met CSS en HTML

Maak een schakelaar met CSS en HTML die er niet alleen als een schakelaar uitzien, maar die ook daadwerkelijk iets doet. Zo’n aan-uitschakelaar (toggle of switch) gebruik je bijvoorbeeld om je webformulier op te fruiten. Of je laat bezoekers van de website kiezen tussen een licht en een donker thema. Met een klein beetje JavaScript maak je een werkende themawisselaar van je schakelaar.

Het mooie is dat de basis van de schakelaar gewoon in je HTML-gereedschapskist zit: het element <input type="checkbox">. Het principe is dat je de in- en uitgeschakelde toestand van dit selectievakje gebruikt om de opmaak van de zelfgebouwde schakelaar te veranderen en om JavaScript-code te activeren. Het selectievakje zelf maak je onzichtbaar.

Het is een eenvoudig project en toch maak je kennis met een berg CSS-mogelijkheden. Op CodePen staat het resultaat en daarmee kun je vrijuit experimenteren.

See the Pen schakelaar by Peter Doolaard (@dool) on CodePen.dark

Stappenplan

  1. Maak van een label een container met een selectievakje.
  2. Definieer een raster in de container.
  3. Verberg het selectievakje visueel.
  4. Maak een nieuwe schakelaar.
  5. Schrijf een paar regels JavaScript voor de themawisselaar.

De HTML voor de schakelaar

Gebruik een element <label> als container en plaats daarin een selectievakje, twee elementen <span> voor de labeltekst en een <span> voor de nieuwe schakelaar.

De hele werking van een schakelaar met CSS is gebaseerd op een <input type="checkbox">. Dit selectievakje houdt zelf bij of het is in- of uitgeschakeld en zet daarbij de status checked aan of uit. In CSS gebruik je de pseudoklasse :checked om te reageren op de in- of uitgeschakelde toestand. Ook in JavaScript lees je de status uit met checked.

Het label is met het attribuut for gekoppeld aan het selectievakje. Dat is nodig voor de toegankelijkheid. Klikken op een label activeert ook het gekoppelde selectievakje, maar omdat de <input> is genest in het label gebeurt dat toch al.

<label class="switch" for="switch-element">
  <span class="switch-label-light">Licht thema</span>
  <input type="checkbox" id="switch-element">
  <span class="switch-bg"></span>
  <span class="switch-label-dark">Donker thema</span>
</label>

Definieer het raster

Bij de traditionele manier om een schakelaar te maken met CSS en HTML positioneer je de onderdelen relatief en absoluut. Met moderne CSS kun je dat deels vervangen door een raster (CSS Grid Layout). Maak in het label drie rasterkolommen. Alle kolommen hebben een flexibele breedte (max-content), zodat labeltekst en schakelaar altijd passen. De middelste kolom is voor de nieuwe schakelaar en het onzichtbare selectievakje. Er is ruimte (column-gap) tussen de kolommen.

.switch {
  column-gap: 0.5rem;
  display: grid;
  grid-template-columns: 
  [label-light-start] max-content 
  [label-light-end switch-start] max-content
  [switch-end label-dark-start] max-content 
  [label-dark-end];
}

De definitie van de kolommen is vrij lang doordat de rasterlijnen een naam hebben. Je ziet dan goed met welk deel van het raster je werkt. Gebruik je liever de lijnnummers, dan kan het korter met:

grid-template-columns: repeat(3, max-content);

Je ziet geen definitie van een rij. Die wordt automatisch gemaakt en is vanzelf zo hoog als nodig is voor de inhoud.

Het selectievakje toegankelijk verbergen

Vervolgens maak je het selectievakje onzichtbaar. Het moet wel toegankelijk blijven. Je kunt het daarom niet verstoppen met een eigenschap als display: none, want daarmee haal je het uit de boomstructuur van de browser. Dan is het ontoegankelijk voor de Tab-toets en screenreaders,  en kun je de schakelaar alleen nog met de muis bedienen. Daarmee sluit je een grote groep gebruikers uit.

Buiten beeld zetten kan ook niet. Er zijn screenreaders voor aanraakschermen die voelbare feedback geven op interactieve elementen. Dan moet dat element wel in beeld en op zijn plek staan. (Lees Inclusively Hiding & Styling Checkboxes and Radio Buttons voor meer achtergrond.) Bovendien moet de tabvolgorde in stand blijven.

Het lijkt nu een ingewikkelde operatie te worden, maar dat valt reuze mee. Je maakt het transparant door de ondoorzichtigheid (opacity) op – bijna – 0 in te stellen.

‘Vroegâh’ moest je ook met absolute positionering het selectievakje uit de flow halen en het daarna weer verschuiven om het op dezelfde plek te zetten als de zichtbare schakelaar. Met CSS Grid Layout stapel je elementen eenvoudig op elkaar door ze in dezelfde rastercel te plaatsen. Het selectievakje komt dus onzichtbaar in de tweede kolom.

De stapelvolgorde aanpassen met z-index

In de stapelvolgorde komt de nieuwe schakelaar boven op het selectievakje te staan. Die dekt daardoor het vakje af. Voor bediening met de muis, het toetsenbord of touch maakt dat niet, maar een screenreader op touchscreen  kan het selectievakje niet bedienen en dus doet de schakelaar niets. Dat los je op door met de z-index het selectievakje hoger in de stapel te zetten.

Je plaatst het element in het raster met de eigenschap grid-area. Omdat de lijnen die dat gebied definiëren switch-start en switch-end heten, wordt automatisch een grid-area met de korte naam switch gemaakt.

.switch [type="checkbox"] {
  grid-area: switch;
  height: 1.5rem;
  margin: 0;
  opacity: 0.00001;
  width: 3rem;
  z-index: 1;
}

Waarom z-index gebruiken en niet gewoon het selectievakje lager in de code zetten? Dat kan niet, want de schakelaar <span class="switch-bg"></span> reageert op de status van het selectievakje. Dan moet de schakelaar lager in de code staan, want je kunt in CSS nog geen ‘parent selector’ zoals :has() gebruiken. Lees daar meer over in het artikel over de nieuwe pseudoklassen in CSS.

Plaats de labels in het raster

Voor elk label heb je één regel CSS nodig.

.switch .switch-label-light {
  grid-area: label-light;
}
.switch .switch-label-dark {
  grid-area: label-dark;
}

Maak een nieuwe schakelaar

In de HTML staat een lege <span> met de klasse .switch-bg. Dat wordt de achtergrond van de schakelaar. Het element komt in de middelste kolom (switch) van het raster, krijgt een breedte en hoogte, ronde hoeken en een achtergrondkleur. Je ziet ook een relatieve positie. Die is nodig om hierna het pseudo-element ::before – de knop van schakelaar – absoluut te kunnen plaatsen in de schakelaar. Zonder die eigenschap positioneer je absoluut in <body>.

.switch .switch-bg {
  grid-area: switch;
  width: 3rem;
  height: 1.5rem;
  background: #ccc;
  border-radius: 1.1rem;
  position: relative;
}

Voeg de knop toe

De knop is een pseudo-element van .switch-bg. Het is een bijna zwart, afgerond vierkant met een kleine marge om het vrijstaand te maken. Het wordt links (left: 0)  op de achtergrond geplaatst. Dat is de standaardpositie. Door die als eigenschap toch toe te voegen, kun je de verplaatsing naar rechts vloeiender maken met een CSS-overgang.

.switch .switch-bg::before {
  content: "";
  width: 1.1rem;
  height: 1.1rem;
  margin: 0.2rem;
  background: #333;
  border-radius: 50%;
  position: absolute;
  left: 0;
  transition: left 200ms ease;
}

Laat de schakelaar schakelen

En dan nu waar het allemaal om begonnen is: flip the switch. Het is echt maar één regel CSS, waarin alles draait om de pseudoklasse :checked en het gegeven dat je de knop met een combinator kunt koppelen aan het selectievakje. Daarom is de volgorde van de HTML zo belangrijk. De nieuwe knop moet direct volgen op het selectievakje (en je selecteert met de combinator +, de aangrenzende sibling).

.switch [type="checkbox"]:checked + .switch-bg::before {
  left: 1.5rem;
}

Inderdaad, het kan ook met de combinator ~ en dan kan de knop lager in de code staan. Maar dat heeft in dit geval geen nut.

Geef de schakelaar focusopmaak

Het selectievakje is bereikbaar met de Tab-toets (en te bedienen met de spatiebalk). Je ziet alleen niet wanneer het geselecteerd is, want met het vakje is ook de focusopmaak onzichtbaar geworden. Met de nieuwe pseudoklasse :focus-visible maak je focus zichtbaar bij toetsenbordbediening, maar niet bij tikken of klikken (dat heeft weinig zin).

Maak eerst de opmaak voor :focus en pas die toe op de schakelaar. Verberg daarna de focusopmaak als de manier van selecteren niet voldoet aan de voorwaarden van :focus-visible. Dat komt neer op: niet met het toetsenbord.

De focuskleur moet in het lichte en het donkere thema zichtbaar zijn. Daarom gebruik je een variabele. De variabele --focus-color wordt verderop op twee plaatsen gedefinieerd:

  • Bij de selector body is de waarde #333, een donkere kleur voor op de lichte achtergrond. Dit is de startwaarde.
  • In de klasse .dark krijgt de variabele een andere waarde: #eee, een lichte kleur. Deze waarde is alleen van toepassing als de klasse is geladen.
.switch [type="checkbox"]:focus + .switch-bg {
  outline-offset: 0.25rem;
  outline: 2px dotted var(--focus-color);
}
.switch [type="checkbox"]:focus:not(:focus-visible) + .switch-bg {
  outline: none;
} 

CSS voor het thema

De schakelaar met CSS en HTML is klaar en werkt, maar doet nog niks. Het uitgangspunt voor de themawisselaar is een licht thema bij een uitgeschakeld vakje. Dan staat de schakelaar naar links. Wordt het vakje ingeschakeld, dan schuift de schakelaar naar rechts en wordt het donkere thema geactiveerd. Bedenk dat de zichtbare schakelaar onbelangrijk is voor de werking van de themawisselaar. Het werk wordt gedaan door het selectievakje.

Het lichte thema gebruikt de browserstandaard met een lichte achtergrond en donkere tekst. Voor het donkere thema maak je een klasse .dark met daarin CSS custom properties oftewel CSS-variabelen.

De klasse met de variabelen ziet er zo uit:

.dark {
  --bg-color: #333;
  --color: #eee;
  --focus-color: #eee;
}

Vervolgens schrijf je een declaratie voor body:

body {
  --focus-color: #333;
  background-color: var(--bg-color, revert);
  color: var(--color, revert);
}

De (achtergrond)kleur krijgt de waarde van de variabele en als de variabele niet beschikbaar is, is de waarde revert. Met revert gaan de kleuren (in dit geval) terug naar de standaardinstelling van de browser. Een waarde achter de komma bij een CSS-variabele is een niet-vereiste terugvalwaarde. Daarmee voorkom je dat een eigenschap geen waarde krijgt als de variabele niet is ingesteld. In dit voorbeeld kan het ook zonder revert, want als je omschakelt naar het lichte thema zijn de variabelen uit de klasse .dark niet meer beschikbaar en krijg je sowieso het zwart op wit uit de browserstylesheet.

Door nu met JavaScript bij het inschakelen van het donkere thema de klasse .dark aan het element <body> toe te voegen krijgen de variabelen hun waarde. Schakel je terug naar het lichte thema dan verwijder je de klasse en gaan de kleuren terug naar de browserstandaard.

Koppel JavaScript-code aan de schakelaar

Zet de schakelaar aan het werk met de volgende JavaScript-code:

const themeRoot = document.querySelector("body");
const switcher = document.querySelector('.switch [type="checkbox"]');
switcher.checked = false;
switcher.addEventListener("change", (event) => {
  if (event.target.checked) {
    themeRoot.classList.add("dark");
    return;
  }
  themeRoot.classList.remove("dark");
});

De eerste twee regels geven via variabelen toegang tot het element <body> en het selectievakje.

De derde regel zorgt ervoor dat bij het laden van de pagina het vakje uit en de schakelaar naar links staat.

De vierde regel laat de browser luisteren naar veranderingen in de status van het selectievakje. Dan wordt een functie uitgevoerd die bij het ingeschakelde vakje (checked) de klasse .dark toevoegt aan de <body> en dan de functie verlaat. Is het vakje niet ingeschakeld, dan wordt de klasse verwijderd.


Goed om te zien dat het artikel je tot het einde toe heeft kunnen interesseren. De meeste artikelen op dit blog worden geschreven door de auteurs van
uitgeverij Van Duuren Media.
Ben je geïnteresseerd in verdere verdieping of meer praktische toepassingen? Klik op onderstaande banner voor het meest actuele overzicht.

Geef een reactie

Deze site gebruikt Akismet om spam te verminderen. Bekijk hoe je reactie-gegevens worden verwerkt.