albert.walickihire me
← all solutions
css

How to disable a link with CSS?

Why pointer-events: none doesn't fully disable a link, and how to disable it properly by removing the href, adding aria-disabled and styling the disabled state.

pointer-events: none stops mouse and touch clicks, but it doesn't really disable a link. The link can still be reached with Tab and opened with Enter, and screen readers still announce it as a working link.

/* Blocks the mouse only: Tab + Enter still open the link */
.link.is-disabled {
  pointer-events: none;
  opacity: 0.5;
}

To disable a link properly, remove its href. An <a> without href isn't focusable, isn't clickable and doesn't go anywhere. Then add aria-disabled="true" and style that attribute:

<!-- Enabled -->
<a href="/checkout" class="button">Checkout</a>

<!-- Disabled -->
<a role="link" aria-disabled="true" class="button">Checkout</a>
.button[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

Without href, the browser no longer exposes the <a> as a link. role="link" gives the role back, so screen readers announce it as a disabled link instead of plain text. You could also style a:not([href]), but that matches every placeholder link on the page, so I prefer the explicit attribute.

Don't swap the URL for # or javascript:void(0). The link stays focusable and clickable, and # jumps to the top of the page.

In React and other frameworks

Render the link without href when it's disabled:

function ButtonLink({ href, disabled, children }) {
  if (disabled) {
    return (
      <a role="link" aria-disabled="true" className="button">
        {children}
      </a>
    );
  }

  return (
    <a href={href} className="button">
      {children}
    </a>
  );
}

Router components like Next.js <Link> need an href, so render a plain <a> or a <span> in the disabled branch instead of passing a fake URL.

Whichever way you disable it, say why nearby, for example "Add an item to your cart first". A grayed-out control with no explanation is frustrating.

If it's an action, use a button

Links navigate. If the "link" saves, deletes, submits or opens a modal, it should be a <button>, which has a real disabled attribute:

<button type="button" class="button" disabled>Delete</button>

A disabled button is skipped by Tab, ignores clicks and is announced as disabled. Style it with .button:disabled. See how to make a button or div work like a link for the opposite case.

Gotcha: cursor: not-allowed doesn't show

With pointer-events: none, the element doesn't receive the pointer at all, so a cursor: not-allowed on the same element never appears. The browser shows the cursor of whatever is underneath. If you can't remove pointer-events: none (for example in a third-party component), put the cursor on a wrapper:

<span class="link-wrapper">
  <a href="/export" class="link is-disabled">Export</a>
</span>
.link-wrapper {
  cursor: not-allowed;
}

.link.is-disabled {
  pointer-events: none;
}

This still only blocks the mouse. Adding tabindex="-1" takes the link out of the tab order, but screen reader users can still find and follow it, so it's a patch, not a fix. More cursor details are in how to change the cursor on hover.

more solutions
work with me

Got something that needs building?

Frontend builds, full-stack features in Django, design-system work. Available for work.

See my workGet in touch