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.