Select The Right Tool For The Job
If you’re using a static file server to serve an HTML page which implements a <select>
to provide user navigation, you cannot achieve path-based navigation without 1) client-side JavaScript, or 2) server-side redirect logic.
In other words, path-based navigation with a <select>
element will break for users where JavaScript fails — fails to load due to a network issue or a user preference, fails to parse, fails to execute, etc. — unless you control the server and can redirect a path with query parameters (/path?foo=bar
) to a pure path (/path/bar
).
The Problem
Many sites use a <select>
element to afford user navigation.
It’s a tidy, spacing-saving solution that collapses a large number of choices into a single UI element, providing navigational options at the time they’re needed.
As a developer, an example of this you might see a lot is the “docs version switcher”.
The neat thing about a <select>
element is that, when clicked, it triggers a menu drawn by the underlying operating system in a manner best suited (and accessible) to the given user’s device and preferences.
In the case of a “docs version switcher”, you interact with the <select>
and it gives you a menu of choices. Once you choose an option, it navigates you to your selected version of the docs.
A common way to create this would be to write the <select>
markup and add a JavaScript handler that redirects the user to the selected version of the docs.
<select onChange={(e) => {
e.preventDefault();
const version = e.target.value;
window.location = "/docs/" + (version === "main" ? "" : version);
}}>
<option value="main">Latest</option>
<option value="6.3.0">6.3.0</option>
<option value="5.x">5.x</option>
</select>
However, without JavaScript that onChange
handler does absolutely nothing.
So, if you’re aiming for a progressively-enhanced experience, how do you use <select>
?
Making Things Work Before (Or Without) JavaScript
By default (and before/without JavaScript), the <select>
element won’t submit anywhere on its own. For that, it needs a <form>
. It also won’t submit when changed. For that it needs a <button>
. Combined, these constitute the semantics in HTML for declaratively describing the navigation a form will make.
<form action="/docs" method="GET">
<select name="version">
<option value="main">Latest</option>
<option value="6.3.0">6.3.0</option>
<option value="5.x">5.x</option>
</select>
<button type="submit">Switch</button>
</form>
When this form submits, it will do a GET to the action
with the form elements parameterized, e.g. /docs?version=6.3.0
Now we have markup properly suited to the task of progressively-enhanced navigation (sprinkle some JS in there and you can hide the submit button, that way if/when JS executes the form submits onChange
and the user is unencumbered by the presence of an unnecessary submit button).
But will this experience work before (or without) JavaScript? Maybe. There’s another caveat.
Are You Using a Static File Server?
If you’re doing static site generation, i.e. creating static HTML files on disk that map to URLs, you’ve got another obstacle to using <select>
: routing.
In the “docs switcher” example, a standard SSG approach would mean architecting your site to generate different versions of the docs within different, nested folders on the file system whose structure will parity your public-facing URLs.
For example, your file structure is:
docs/
├── index.html
├── foo/
│ └── ...
├── bar/
│ └── ...
└── 6.3.0/
│ ├── index.html
│ ├── foo/
│ │ └── ...
│ ├── bar/
│ │ └── ...
└── 5.x/
├── index.html
└── old-foo/
This would result in the following URLs:
/docs/
Latest version of the docs/docs/6.3.0/
Specific version of the docs/docs/5.x/
Previous major version of the docs
However, as noted, by default the browser uses <select>
to create a parameterized request. And for a static file server, a request to the same path but with different query parameters will result in the same response, e.g. a request for /docs?version=5.3.0
will result in the same response as a request for /docs?version=3.2.0
.
Routing based on query parameters is impossible for SSG without assuming 1) working JavaScript on the client, or 2) control over a (host-specific) request/response mechanism on the server.
This presents a problem for you: you crafted a nice solution built on the native, semantic primitives of the web — the <form>
, <select>
, and <button>
elements — but now you have a crisis around your site’s architecture because of how it’s built and hosted.
Now you have to consider your options. Here are a few:
- Stop caring about a progressively-enhanced solution.
- Pro: easy.
- Con: if a browser can’t (or won’t) run your JavaScript, the user can’t navigate your site.
- Implement a redirect on the server which takes the
/docs?version=*
request from the browser and redirects it to the appropriate/docs/*
path.- Pro: maintain your current UI solution using a
<select>
. - Con: now you’re taking control of the server, which is what you were likely trying to avoid with an SSG approach, and relying on the host-specific escape hatch for server-side control in a static file server environment.
- Pro: maintain your current UI solution using a
- Re-think your UI solution of a “docs switcher” from a
<select>
element to something more tailored to your site’s architecture, e.g a list of links whosehref
attributes you can control for path-based routing.- Pro: maintains progressive enhancement and lets you control routing without needing server-side control.
- Con: can’t leverage the UI benefits of a
<select>
— now or in the future.
- Consider re-architecting how your site is built and hosted.
- Pro: will likely scale with you in the future as you encounter other static site limitations and creeping lock-in from host-specific escape hatches.
- Con: who wants to re-architect their entire site just to use a
<select>
?
Working With The Materials of The Web
Probably more than you ever wanted to know about <select>
, I know.
But it’s intended to illustrate how interconnected our decision making can be when creating a website. If you’re planning on providing a universally-accessible, progressively-enhanced experience, then which HTML element you use can have ramifications for your choice of server and host.
Using <select>
instead of <a>
has its ramifications and tradeoffs (both on you as a developer and on your users). In the case of a <select>
element, it visually collapses a large number of options into a single point of interaction whose UI is (in large part) under the control of the operating system. But functionally it is a <form>
request which works by parameterizing the request URL.
A form like this:
<form action="/docs" method="GET">
<select name="version">
<option value="main">Latest</option>
<option value="6.3.0">6.3.0</option>
<option value="5.x">5.x</option>
</select>
<button type="submit">Switch</button>
</form>
Is a navigation on the web. Navigationally, it is similar to a set of links like this:
<a href="/docs?version=main">Latest</a>
<a href="/docs?version=6.3.0">6.3.0</a>
<a href="/docs?version=5.x">5.x</a>
A link gives you, the developer, more declarative control over navigation (you can easily change the href
from query parameters to paths, for example). But it’s also a different experience for the end user. A <select>
hides navigation options behind a menu while a set of links is revealed all at once.
You could, for example, try to mimic hiding a set of options behind a user interaction using something like <details>
:
<details>
<summary>Version: 6.3.0</summary>
<ul>
<li><a href="/docs/main">Latest</a></li>
<li><a href="/docs/6.3.0">6.3.0</a></li>
<li><a href="/docs/5.x">5.x</a></li>
</ul>
</details>
But it’s still not equivalent. For example, <select>
triggers a menu drawn by the respective OS that’s hard to style while <details>
triggers an unfolding menu that’s more amenable to styling.
The point is, when considering a progressively-enhanced experience that will be accessible to the widest range of users, your technology choices are interconnected and opinionated. Some play nice with each other, others do not.
Conclusion
Choosing technologies that align themselves with the grain of the web as well as its underlying principles provides you the option to scale your solution up or down in complexity while preserving choice. You choose the web primitives that best fit your problem at hand — e.g. an <a>
element or a combination of the <form>
, <select>
, and <button>
elements — and bypass the need for custom escape hatches tailored to individual technologies.
Web-aligned technologies encourage you to ask, “How can I make this work best for my users?” This stands in contrast to technologies whose opinions burden you to ask, “How can I make this work at all?”
It’s a good bet to invest in tools aligned with the way the web is designed to work.