CSS-knopstatussen zijn de stijlen die een knop toont als er met de muis overheen wordt gegaan (:hover), als hij met het toetsenbord focus krijgt (:focus-visible), wordt ingedrukt (:active), uitgeschakeld is (:disabled of aria-disabled) of aan het laden is. Hier staat één knop in elke status, met kopieerbare gewone CSS en Tailwind, plus outline-, ghost-, sluit- en icoonknoppen die toegankelijk blijven.
Een knop heeft zes statussen die het ontwerpen waard zijn: standaard, hover, focus-visible, active, disabled en laden. De meeste codevoorbeelden online tonen er een of twee, op verschillende knoppen, met verschillende code. Deze gids gebruikt van begin tot eind één knop, toont elke status naast elkaar en geeft voor elke status kopieerbare gewone CSS en Tailwind. Daarna behandelt hij de varianten waar het meest naar gezocht wordt (outline, ghost, sluiten, icoon) en het verschil tussen disabled en aria-disabled, dat meer is dan een stijlkeuze.
Elke tegel hieronder is in één status getekend, zodat u ze in één oogopslag kunt vergelijken. Met de live knop onder het raster kunt u een status afdwingen of hem uitproberen met uw eigen muis en toetsenbord.
| Status | CSS-selector | Tailwind-variant | Wat activeert hem |
|---|---|---|---|
| Standaard | .btn | (geen prefix) | Niets |
| Hover | .btn:hover | hover: | Een aanwijzer boven de knop. Touchscreens hebben geen echte hover. |
| Focus-visible | .btn:focus-visible | focus-visible: | Focus waarvan de browser bepaalt dat die getoond moet worden, meestal toetsenbordfocus (Tab) |
| Focus | .btn:focus | focus: | Elke focus, in de meeste browsers ook een muisklik |
| Active | .btn:active | active: | Zolang de muisknop, vinger of spatiebalk ingedrukt wordt gehouden |
| Disabled | .btn:disabled | disabled: | Het attribuut disabled |
| Disabled (ARIA) | .btn[aria-disabled="true"] | aria-disabled: | Het attribuut aria-disabled="true" |
| Laden | .btn[aria-busy="true"] | aria-busy: | Uw script, terwijl het werk bezig is |
In gewone CSS doet de volgorde ertoe als de selectors dezelfde specificiteit hebben: zet eerst :hover, dan :focus-visible en dan :active, zodat indrukken wint van hoveren. Tailwind sorteert zijn varianten voor u.
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
min-height: 2.5rem; /* 40px, above the 24px WCAG minimum */
padding: 0.625rem 1rem;
border: 1px solid transparent; /* keeps size equal to the outline variant */
border-radius: 0.5rem;
background: #2563eb;
color: #fff;
font: 600 0.875rem/1.25rem system-ui, sans-serif;
cursor: pointer;
transition: background-color 150ms, transform 150ms;
}
/* hover only when the button can actually be used */
.btn:hover:not(:disabled, [aria-disabled="true"]) {
background: #1d4ed8;
}
/* keyboard focus; mouse clicks do not show the ring */
.btn:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
.btn:active:not(:disabled, [aria-disabled="true"]) {
background: #1e40af;
transform: translateY(1px);
}
.btn:disabled,
.btn[aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
}
.btn[aria-busy="true"] {
cursor: progress;
}
@media (prefers-reduced-motion: reduce) {
.btn { transition: none; }
.btn:active { transform: none; }
}
Drie details doen het meeste werk. De bewaker :not(:disabled, [aria-disabled="true"]) voorkomt dat een uitgeschakelde knop reageert op hover of indrukken, waardoor hij anders klikbaar lijkt. De transparante rand houdt een gevulde knop even groot als een outline-knop, zodat wisselen van variant de lay-out niet laat verspringen. En :focus-visible in plaats van :focus zorgt ervoor dat muisgebruikers na een klik geen ring zien, terwijl toetsenbordgebruikers hem altijd zien.
Gebruik hem zo: <button type="button" class="btn">Save changes</button>. Stel altijd type in: een <button> binnen een formulier is standaard type="submit" en verstuurt het formulier bij een klik.
<button type="button"
class="inline-flex items-center justify-center gap-2 rounded-lg border border-transparent
bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white transition
hover:bg-blue-700 active:translate-y-px active:bg-blue-800
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600
disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-blue-600 disabled:active:translate-y-0
aria-disabled:cursor-not-allowed aria-disabled:opacity-50
motion-reduce:transition-none">
Save changes
</button>
Deze classes werken in Tailwind 3.4 en 4. Twee opmerkingen over v4: hover: geldt alleen op apparaten die hover ondersteunen (@media (hover: hover)), en de basisstijlen van v4 geven knoppen cursor: default, dus voeg cursor-pointer toe als u de handcursor wilt. Zie de Tailwind v4-cheatsheet voor de andere gewijzigde standaardwaarden, zoals de ring van 1px.
WCAG 2.4.7 Focus Visible (niveau AA) vereist dat toetsenbordfocus zichtbaar is. De browseromlijning weghalen met outline: none zonder iets terug te zetten, voldoet daar niet aan. Richtlijnen die standhouden:
:focus-visible voor de ring. Het wordt ondersteund in alle huidige grote browsers, dus de oude workaround :focus:not(:focus-visible) is niet meer nodig.outline dan box-shadow. Omlijningen blijven zichtbaar in de geforceerde-kleurenmodus van Windows (hoog contrast), waar box shadows worden verwijderd.outline-offset van 2px laat ruimte tussen de knop en de ring, zodat een blauwe ring nog opvalt tegen een blauwe knop.Een ladende knop moet vier dingen doen: laten zien dat er gewerkt wordt, een tweede verzending blokkeren, de toetsenbordfocus houden waar die was, en schermlezergebruikers laten weten wanneer het werk klaar is. Tijdens het verzoek disabled instellen breekt het derde punt: de knop verliest de focus en in sommige browsers belanden toetsenbordgebruikers weer bovenaan de pagina. Gebruik in plaats daarvan aria-disabled en een controle in uw script.
<button type="submit" class="btn" aria-disabled="true" aria-busy="true">
<svg class="spinner" viewBox="0 0 24 24" width="16" height="16" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-opacity=".3" stroke-width="3"/>
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
</svg>
Saving
</button>
<!-- one live region on the page, outside the button -->
<p class="visually-hidden" role="status">Saving your changes</p>
.spinner { animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
.spinner { animation-duration: 2.4s; } /* slow it, keep a sign of progress */
}
.visually-hidden {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
form.addEventListener("submit", async (event) => {
event.preventDefault();
if (button.getAttribute("aria-disabled") === "true") return; // ignore double clicks
button.setAttribute("aria-disabled", "true");
button.setAttribute("aria-busy", "true");
status.textContent = "Saving your changes";
try {
await save(new FormData(form));
status.textContent = "Saved";
} catch {
status.textContent = "Could not save. Try again.";
} finally {
button.removeAttribute("aria-disabled");
button.removeAttribute("aria-busy");
}
});
Houd het label een werkwoord ("Opslaan", "Verzenden") in plaats van het te vervangen door alleen een spinner, houd de breedte van de knop vast (een min-width voorkomt dat hij krimpt als de tekst verandert) en meld het resultaat via de statusregio, niet alleen door de knoptekst te wijzigen. aria-busy is een hint dat het element wordt bijgewerkt; schermlezers gaan er verschillend mee om, en daarom doet het statusbericht de eigenlijke aankondiging.
Beide laten hulptechnologie de knop als niet beschikbaar aankondigen (vaak als "gedimd" of "niet beschikbaar"). Het verschil zit in het gedrag. Zoals MDN het zegt: het HTML-attribuut disabled schakelt alle functionaliteit uit, terwijl aria-disabled="true" alleen de uitgeschakelde status doorgeeft, dus het gedrag moet u zelf onderdrukken.
disabled | aria-disabled="true" | |
|---|---|---|
| Bereikbaar met Tab | Nee, hij verdwijnt uit de tabvolgorde | Ja, hij blijft focusbaar |
| Activering via klik en toetsenbord | Geblokkeerd door de browser | Vinden nog steeds plaats; uw script moet ze negeren |
| Verstuurt een formulier | Nee. Naam en waarde worden niet meegestuurd, en als het de standaardknop van het formulier is, verstuurt Enter in een veld niets | Ja, tenzij u preventDefault() aanroept |
| Kan bij focus een tooltip of uitleg tonen | Nee, hij kan geen focus krijgen | Ja, met aria-describedby |
| Stijlhaak | :disabled / disabled: | [aria-disabled="true"] / aria-disabled: |
Werkt op links (<a>) | Nee, disabled is niet geldig op links | Ja, in combinatie met het verwijderen van href |
Vuistregel: gebruik disabled als de knop echt niet bruikbaar is en niemand hoeft te ontdekken waarom, bijvoorbeeld een formulierelement in een sectie die is uitgeschakeld. Gebruik aria-disabled als een gebruiker de knop moet kunnen vinden en moet leren wat er ontbreekt ("Vul uw e-mailadres in om verder te gaan"), en tijdens het laden. Vaak is het beste antwoord geen van beide: laat de knop ingeschakeld en toon een duidelijke foutmelding als hij wordt ingedrukt terwijl er invoer ontbreekt.
button.addEventListener("click", (event) => {
if (button.getAttribute("aria-disabled") === "true") {
event.preventDefault(); // also stops a submit button from submitting
return;
}
doTheThing();
});
Over contrast: WCAG 1.4.3 zondert tekst in inactieve (uitgeschakelde) componenten uit van de minimale contrastregel, en daarom is een vervaagde knop met 50% dekking toegestaan. Toch is het de moeite waard het label leesbaar te houden, want mensen moeten zien wat ze nog niet kunnen doen.
Een outline-knop heeft een transparante vulling en een zichtbare rand; een ghostknop heeft geen van beide tot er met de muis overheen wordt gegaan. Gebruik ze voor minder belangrijke acties naast een gevulde primaire knop: outline voor een duidelijke tweede keuze ("Voorbeeld"), ghost voor rustige acties in werkbalken en menu's ("Annuleren", "Bewerken").
Dit zijn echte knoppen: ga er met de muis overheen en druk op Tab om elke status te zien. CSS voor de varianten, bovenop de basis .btn van hierboven:
/* Outline: transparent fill, visible border */
.btn-outline {
background: transparent;
color: #1d4ed8;
border-color: #2563eb;
}
.btn-outline:hover:not(:disabled, [aria-disabled="true"]) { background: #eff6ff; }
.btn-outline:active:not(:disabled, [aria-disabled="true"]) { background: #dbeafe; }
/* Ghost: no fill, no visible border until hover */
.btn-ghost {
background: transparent;
color: #1d4ed8;
border-color: transparent;
}
.btn-ghost:hover:not(:disabled, [aria-disabled="true"]) { background: #eff6ff; }
.btn-ghost:active:not(:disabled, [aria-disabled="true"]) { background: #dbeafe; }
/* Destructive */
.btn-danger { background: #dc2626; }
.btn-danger:hover:not(:disabled, [aria-disabled="true"]) { background: #b91c1c; }
.btn-danger:focus-visible { outline-color: #dc2626; }
<!-- Outline -->
<button type="button" class="inline-flex items-center gap-2 rounded-lg border border-blue-600 bg-transparent px-4 py-2.5 text-sm font-semibold text-blue-700 hover:bg-blue-50 active:bg-blue-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-50">
Outline
</button>
<!-- Ghost -->
<button type="button" class="inline-flex items-center gap-2 rounded-lg border border-transparent px-4 py-2.5 text-sm font-semibold text-blue-700 hover:bg-blue-50 active:bg-blue-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-50">
Ghost
</button>
<!-- Destructive -->
<button type="button" class="inline-flex items-center gap-2 rounded-lg border border-transparent bg-red-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-red-700 active:bg-red-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:opacity-50">
Delete project
</button>
WCAG 1.4.11 Non-text Contrast vraagt om 3:1 ten opzichte van aangrenzende kleuren voor de delen van een element die nodig zijn om het te herkennen. De blue-600-rand van de outline-knop op wit haalt dat. Een ghostknop heeft in rust geen begrenzing, dus hij moet het hebben van zijn label en plaats om klikbaar te lijken: zet ghostknoppen waar mensen acties verwachten, zoals werkbalken, dialoogvensters en kaartvoetteksten, en houd hun tekstkleur donker genoeg voor normaal tekstcontrast (4.5:1).
Een X-icoon heeft geen tekst, dus het heeft een toegankelijke naam nodig. Zet aria-label="Close" op de knop en aria-hidden="true" op de SVG, zodat schermlezers "Sluiten, knop" aankondigen in plaats van niets of een bestandsnaam. Heeft een pagina meerdere sluitknoppen, maak de namen dan specifiek: "Cookiemelding sluiten", "Dialoogvenster sluiten".
Uw wijzigingen zijn opgeslagen.
<button type="button" class="btn-close" aria-label="Close">
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false">
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
.btn-close {
display: inline-grid;
place-items: center;
width: 2.5rem;
height: 2.5rem; /* 40px target; WCAG 2.2 AA minimum is 24 by 24 */
padding: 0;
border: 0;
border-radius: 9999px;
background: transparent;
color: #334155;
cursor: pointer;
}
.btn-close:hover { background: rgb(15 23 42 / 0.08); }
.btn-close:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
<button type="button" aria-label="Close"
class="inline-grid size-10 place-items-center rounded-full text-slate-700 hover:bg-slate-900/10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">
<svg viewBox="0 0 24 24" class="size-5" aria-hidden="true" focusable="false">
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
Bepaal de grootte van het klikgebied, niet alleen van het icoon. WCAG 2.5.8 Target Size (Minimum), niveau AA in WCAG 2.2, vraagt om doelen van minstens 24 bij 24 CSS-pixels (met uitzonderingen, zoals genoeg ruimte rond een kleiner doel). De knop van 40px hierboven haalt dat ruim, terwijl de getekende X 20px blijft. Gebruik de letter "x" of het teken "×" niet als enige inhoud zonder label: schermlezers lezen het voor als "x" of "maal".
<!-- Icon + text: the text is the name, the icon is decoration -->
<button type="button" class="btn">
<svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 24 24">...</svg>
Download PDF
</button>
<!-- Icon only, option 1: aria-label -->
<button type="button" class="btn btn-icon" aria-label="Download PDF">
<svg aria-hidden="true" focusable="false" width="20" height="20" viewBox="0 0 24 24">...</svg>
</button>
<!-- Icon only, option 2: visually hidden text (gets translated by browser translation tools) -->
<button type="button" class="btn btn-icon">
<svg aria-hidden="true" focusable="false" width="20" height="20" viewBox="0 0 24 24">...</svg>
<span class="visually-hidden">Download PDF</span>
</button>
.btn svg { flex: none; } /* keep icons from squashing */
.btn-icon {
width: 2.5rem;
padding: 0; /* square: same width and height */
}
<button type="button" class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">
<svg class="size-4 shrink-0" aria-hidden="true" ...></svg>
Download PDF
</button>
<button type="button" aria-label="Download PDF" class="inline-grid size-10 place-items-center rounded-lg bg-blue-600 text-white hover:bg-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">
<svg class="size-5" aria-hidden="true" ...></svg>
</button>
aria-hidden="true") en laat de tekst de naam zijn. Gebruik gap in plaats van een marge op het icoon, zodat de tussenruimte blijft kloppen als het icoon wordt verwijderd of naar rechts wordt verplaatst.aria-label of visueel verborgen tekst. Vertrouw niet op title: browsers gebruiken het als terugvalnaam, maar de tooltip verschijnt alleen bij muishover, niet bij toetsenbordfocus of aanraking.focusable="false" toe aan inline SVG's als u nog oude versies van Internet Explorer ondersteunt, die van SVG's tabstops maakten. Moderne browsers hebben het niet nodig, maar het kan geen kwaad.<button> (voor acties) of een <a href> (voor navigatie), geen klikbare <div>.type="button", tenzij hij een formulier moet versturen.prefers-reduced-motion.Wilt u een knop visueel ontwerpen en de CSS kopiëren, gebruik dan de CSS-knopgenerator. Voor kant-en-klare sets kunt u de knopcomponenten en geanimeerde knoppen bekijken. Twee onderdelen die goed passen bij deze gids: de HTML- en CSS-knopset met primaire, secundaire, outline-, ghost-, icoon-, laad- en destructieve stijlen, en de verzameling CSS-knopstijlen met kopieerbare HTML en CSS. Lees voorbeelden van calls to action voor waar knoppen op een pagina staan en wat ze moeten zeggen.
Richt u op de pseudo-class :disabled, bijvoorbeeld .btn:disabled { opacity: 0.5; cursor: not-allowed; }. Gebruikt u ook aria-disabled, voeg dan [aria-disabled="true"] toe aan dezelfde regel. Bescherm uw hover- en active-stijlen met :not(:disabled), zodat een uitgeschakelde knop niet van kleur verandert als er met de muis overheen wordt gegaan of als hij wordt ingedrukt.
disabled haalt de knop uit de tabvolgorde, blokkeert klikken en voorkomt dat hij een formulier verstuurt. aria-disabled="true" vertelt hulptechnologie alleen dat de knop niet beschikbaar is: hij blijft focusbaar en klikbaar, dus uw script moet de activering negeren. Gebruik aria-disabled als gebruikers de knop moeten kunnen focussen om te ontdekken waarom hij niet beschikbaar is, of terwijl een verzoek laadt.
Gebruik :focus-visible voor de zichtbare ring. De browser toont hem bij toetsenbordfocus maar meestal niet na een muisklik, zodat toetsenbordgebruikers altijd zien waar ze zijn zonder dat er bij elke klik een ring verschijnt. Verwijder de omlijning nooit zonder een zichtbare vervanging toe te voegen, want WCAG 2.4.7 vereist een zichtbare focusindicator.
Gebruik een echt button-element met type="button" en aria-label="Close" (of een specifiekere naam zoals "Dialoogvenster sluiten"), en zet aria-hidden="true" op het X-icoon. Maak het klikbare gebied minstens 24 bij 24 CSS-pixels, het WCAG 2.2 AA-minimum; 40 tot 44 pixels is makkelijker te raken.
Een outline-knop heeft een transparante achtergrond en een zichtbare rand, dus in rust is hij herkenbaar als knop. Een ghostknop heeft geen achtergrond en geen zichtbare rand tot er met de muis overheen wordt gegaan, dus hij moet het hebben van zijn tekst en positie. Gebruik outline voor een duidelijke secundaire actie en ghost voor rustige acties in werkbalken of menu's.
Voeg een spinner toe met aria-hidden="true", verander het label in een werkwoord zoals Opslaan, stel aria-disabled="true" in zodat een tweede klik wordt genegeerd terwijl de focus op de knop blijft, en meld het resultaat via een role="status"-regio. Verwijder de attributen als het verzoek klaar is of mislukt.
Uw wijzigingen worden opgeslagen
Opgeslagen
Een status afdwingen
live (probeer het)
Opslaan
Wijzigingen opslaan
Ga erover met de muis, tab ernaartoe, druk hem in of klik om het laden te zien.
De status {state} wordt getoond.
Klikgebeurtenis afgevuurd, script heeft hem genegeerd. Vul uw e-mailadres in om verder te gaan.
Klik op beide en druk dan op Tab om erdoorheen te gaan: maar één is bereikbaar.
Doorgaan
Ontvangen klikgebeurtenissen: {n}
Gratis beginnen · Geen creditcard nodig