CSS-knopstatussen: hover, focus, active, disabled, outline- en icoonknoppen

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.

De statusmatrix: één knop, zes statussen

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.

Wijzigingen opslaan
Standaard. Rusttoestand. Wit op blue-600 (#2563eb) geeft een contrast van ongeveer 5.2:1.
Wijzigingen opslaan
Hover. De aanwijzer staat erop. Eén stap donkerder, geen verschuiving in de lay-out.
Wijzigingen opslaan
Focus-visible. Bereikt via het toetsenbord. Een ring van 2px met 2px tussenruimte, zodat hij op elke achtergrond zichtbaar is.
Wijzigingen opslaan
Active. Tijdens het indrukken. Nog donkerder en 1px omlaag geschoven.
Wijzigingen opslaan
Disabled. Niet beschikbaar. Vervaagd, geen hover, geen handcursor.
Opslaan
Laden. Bezig. Een spinner plus een werkwoord dat zegt wat er gebeurt.
StatusCSS-selectorTailwind-variantWat activeert hem
Standaard.btn(geen prefix)Niets
Hover.btn:hoverhover:Een aanwijzer boven de knop. Touchscreens hebben geen echte hover.
Focus-visible.btn:focus-visiblefocus-visible:Focus waarvan de browser bepaalt dat die getoond moet worden, meestal toetsenbordfocus (Tab)
Focus.btn:focusfocus:Elke focus, in de meeste browsers ook een muisklik
Active.btn:activeactive:Zolang de muisknop, vinger of spatiebalk ingedrukt wordt gehouden
Disabled.btn:disableddisabled: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.

De volledige knop in gewone CSS

.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.

Dezelfde knop in Tailwind

<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.

Focus-visible: de status die u niet kunt overslaan

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:

De laadstatus

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.

disabled vs aria-disabled: het echte verschil

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.

disabledaria-disabled="true"
Bereikbaar met TabNee, hij verdwijnt uit de tabvolgordeJa, hij blijft focusbaar
Activering via klik en toetsenbordGeblokkeerd door de browserVinden nog steeds plaats; uw script moet ze negeren
Verstuurt een formulierNee. Naam en waarde worden niet meegestuurd, en als het de standaardknop van het formulier is, verstuurt Enter in een veld nietsJa, tenzij u preventDefault() aanroept
Kan bij focus een tooltip of uitleg tonenNee, hij kan geen focus krijgenJa, met aria-describedby
Stijlhaak:disabled / disabled:[aria-disabled="true"] / aria-disabled:
Werkt op links (<a>)Nee, disabled is niet geldig op linksJa, 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.

Outline- vs ghostknoppen

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).

Sluitknop (X)

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".

Knoppen met icoon en tekst, en alleen-icoonknoppen

<!-- 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>

Checklist voordat u een knop live zet

Meer knopstijlen

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.

Veelgestelde vragen

Hoe style ik een uitgeschakelde knop in CSS?

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.

Wat is het verschil tussen disabled en aria-disabled?

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.

Moet ik :focus of :focus-visible gebruiken voor knoppen?

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.

Hoe maak ik een toegankelijke sluitknop (X)?

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.

Wat is het verschil tussen een outline-knop en een ghostknop?

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.

Hoe toon ik een laadstatus op een knop?

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

Gerelateerd: Gidsen

Product

Voor wie

Functies

Bronnen

Bedrijf

Sitemap