How to make a button or div work like a link?
Use a real link styled as a button instead of a clickable div or button, make a whole card clickable, and navigate with JavaScript only when you really need to.
If clicking it takes the user to another page, make it a link: an <a href> styled to look like a button.
<a href="/pricing" class="button">See pricing</a>.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1.25rem;
border-radius: 0.5rem;
background: #2563eb;
color: #fff;
font-weight: 600;
text-decoration: none;
}
.button:hover {
background: #1d4ed8;
}
.button:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: 2px;
}A real link gives you things a clickable <div> or <button> doesn't:
- Cmd or Ctrl + click and middle-click open it in a new tab, and right-click offers "Copy link".
- The browser shows the URL when you hover over it.
- Search engine crawlers follow it.
- Screen readers announce it as a link and list it with the other links on the page.
- It works before your JavaScript has loaded, and if it fails to load.
In React, the <Link> components from Next.js and React Router render an <a>, so style that.
Avoid onclick navigation
<!-- Don't do this -->
<div class="button" onclick="location.href = '/pricing'">
See pricing
</div>A <div> can't be focused with the keyboard and has no role, so screen readers don't know it's clickable, and none of the list above works. Adding tabindex="0", role="link" and a key handler fixes part of it, but then you're rebuilding a link, badly. A <button> with the same onclick can be focused, but it's announced as a button and still can't be opened in a new tab.
Also, don't put a <button> inside an <a> or the other way around. Interactive elements inside each other are invalid HTML and behave differently across browsers and screen readers.
A clickable card
You can wrap a whole card in <a>, but screen readers then read everything in the card as the link text. A cleaner pattern is a real link on the title, stretched over the card with a pseudo-element:
<article class="card">
<img src="/posts/grid.jpg" alt="">
<h3>
<a href="/blog/css-grid" class="card-link">
A practical guide to CSS grid
</a>
</h3>
<p>Build real page layouts with grid-template-areas.</p>
</article>.card {
position: relative;
}
.card-link::after {
content: '';
position: absolute;
inset: 0;
}
.card-link:focus-visible {
outline: none;
}
.card:has(.card-link:focus-visible) {
outline: 2px solid #2563eb;
outline-offset: 2px;
}The whole card is clickable, the link text is only the title, and keyboard focus outlines the whole card. Other links or buttons inside the card need position: relative and a z-index to sit above the stretched link. The downside is that the card's text can't be selected with the mouse, because the link covers it.
Navigating from a form
If a button should go to a URL built from form fields, let the form do it. A form with method="get" navigates to its action with the fields in the query string, and formaction gives a button its own URL:
<form action="/search" method="get">
<input type="search" name="q">
<button>Search</button>
<button formaction="/search/advanced">Advanced search</button>
</form>The first button goes to /search?q=... and the second to /search/advanced?q=....
When you really need JavaScript
Sometimes navigation has to wait for something async, like saving a draft. Then navigate from code:
async function saveAndContinue() {
await saveDraft();
location.assign('/checkout');
}location.href = '/checkout'does the same aslocation.assign().location.replace('/checkout')doesn't add a history entry, so the back button skips the current page.window.open(url, '_blank', 'noopener')opens a new tab. Browsers may block it as a popup when it doesn't run directly in response to a click, for example after a slow request.
For redirects that happen as soon as a page loads, see how to redirect to another page in HTML.