Navigate back to Articles page

Navigate to Development topic

Links and buttons and JavaScript, oh my!

The software teams I have worked with over the last 10 years have primarily developed single-page applications (SPA). I published the article Warning signs of front-end complexity as a way to reflect on the problems encountered with this approach and to provide a way forward. Since then, I directed two software teams on migrating their single-page architecture to a multi-page architecture.

When relying on client-side routing for so long, it can be easy to forget how else to do it. As such, developers have asked me during these architecture migrations:

Should this be a link or a button?

My most effective response is simply:

How would you build this if you could not use JavaScript?

A JavaScript-less web relies on links and form submissions to handle all interactions with the server, resulting in a page redirect to the same or a different page. There are also ways to use links and buttons that do not involve the server. Let's review some examples:

Use links when user input is not needed, such as for a website's navigation.

Primary navigation:

<nav aria-label="Primary">
	<a href="/">Home</a>
	<a href="/search">Search</a>
	<a href="/about">About</a>
</nav>

If a page links to itself, it may include extra data in its query string to support dynamic server-side rendering. For example, page and sort keys could provide basic pagination functionality.

Pagination on Page 2 of the search page:

<nav aria-label="Pagination">
	<a href="/search?page=1&sort=asc">
		Page 1
	</a>
	<a href="/search?page=2&sort=asc" aria-current="page">
		Page 2
	</a>
	<a href="/search?page=3&sort=asc">
		Page 3
	</a>
</nav>

Links can also be used to apply settings for a page. Like pagination, changing the sort order uses the page and sort keys.

Change sort order on the search page:

<nav aria-label="Sort">
	<a href="/search?page=1&sort=asc" aria-current="true">
		Sort A to Z
	</a>
	<a href="/search?page=1&sort=desc">
		Sort Z to A
	</a>
</nav>

The server inspects the current sort value in the URL and dynamically applies it to all the pagination links.

Pagination after changing the sort order from ascending to descending:

<nav aria-label="Pagination">
	<a href="/search?page=1&sort=desc" aria-current="page">
		Page 1
	</a>
	<a href="/search?page=2&sort=desc">
		Page 2
	</a>
	<a href="/search?page=3&sort=desc">
		Page 3
	</a>
</nav>

Use in-page links with # in the href value to navigate to specific content on the same page. This is commonly used for:

  • table of contents for pages with many distinct sections
  • skip links at the top of the page for keyboard users
  • back-to-top link (# or #top) at the bottom of a long page

Table of contents:

<nav aria-label="Contents">
	<a href="#section-1">…</a>
	<a href="#section-2">…</a>
	<a href="#section-3">…</a>
</nav>

<h2 id="#section-1">…</h2>
<!-- Content -->

<h2 id="#section-2">…</h2>
<!-- Content -->

<h2 id="#section-3">…</h2>
<!-- Content -->

Skip links:

<nav aria-label="Skip">
	<a href="#main">Skip to main content</a>
	<a href="#results">Skip to results</a>
</nav>

<main id="main">
	<!-- Content -->

	<section id="results">
		<!-- Content -->
	</section>
</main>

Back-to-top link:

<main>
	<!-- Content -->

	<a href="#">Back to top</a>
</main>

Submit buttons

Use submit buttons with forms when user input is needed. The name and value attributes on form fields are sent to the server on submit.

Use a form's get method to change the view of a page based on user input, such as displaying a list of items sorted in a user-specified way. In this particular case, the result is effectively identical to the previously described link-based solution.

Form for changing the sort order:

<form method="get" action="/search">
	<label for="sort-field">Sort</label>
	<select id="sort-field" name="sort">
		<option value="asc">A to Z</option>
		<option value="desc">Z to A</option>
	</select>
	<button type="submit">Sort</button>
</form>

Use a form's post method to create, update, or delete resources. Hidden input fields can indicate the particular action to take on the given resource (Option 1). Alternatively, the hidden input fields could be replaced by a convention that includes the action and resource identifier in the form's action URL (Option 2).

Create an "Item":

<!-- Option 1 -->
<form action="/items" method="post">
	<input name="action" type="hidden" value="create">
	<label for="field">Item</label>
	<input id="field" name="label" type="text">
	<button type="submit">Add</button>
</form>

<!-- Option 2 -->
<form action="/items/new" method="post">
	<label id="field">Item</label>
	<input id="field" name="label" type="text">
	<button type="submit">Add</button>
</form>

Update an "Item":

<!-- Option 1 -->
<form action="/items" method="post">
	<input name="action" type="hidden" value="update">
	<input name="id" type="hidden" value="123">
	<label for="field">Item</label>
	<input id="field" name="label" type="text" value="…">
	<button type="submit">Save</button>
</form>

<!-- Option 2 -->
<form action="/items/123/edit" method="post">
	<label for="field">Item</label>
	<input id="field" name="label" type="text" value="…">
	<button type="submit">Save</button>
</form>

Delete an "Item":

<!-- Option 1 -->
<form action="/items" method="post">
	<input name="action" type="hidden" value="delete">
	<input name="id" type="hidden" value="123">
	<button type="submit">Delete</button>
</form>

<!-- Option 2 -->
<form action="/items/123/delete" method="post">
	<button type="submit">Delete</button>
</form>

Non-submit buttons

Buttons do not submit a form when they are:

  • outside a form
  • within a form with the type="button" attribute

Buttons without submit behavior:

<button>Clear</button>

<form>
	<button type="button">Cancel</button>
</form>

The only way to make these types of buttons functional is to use JavaScript. Prefer to use these for non-critical functionality, that enhances the user experience. For example, a "Clear" button could be a one-click convenience for clearing a text field. If the button fails to work for whatever reason, the user can still manually clear the text field with the keyboard.

Conclusion

How links or buttons should be used primarily depends on the need for user input and the intended default browser behavior.

User input Default behavior Use
None Redirect <a href="/…">
None Skip <a href="#…">
Optional Redirect <button type="submit">
None None (use JavaScript) <button type="button">

Only override defaults with JavaScript if it will enhance the user experience. For example:

  • Prevent a form from submitting in order to provide custom client-side validation.
  • Use a button instead of making another element (like a div) behave as a button.

Overall, it is more reliable and maintainable to align as much as possible with default browser behavior. Use native HTML elements in the way they were intended. This saves development time and circumvents incidental usability and accessibility issues.