A while back, Jake Archibald tweeted:
Don't render buttons on the server that require JS to work.
The idea is that user interface elements which depend on JavaScript (such as buttons) should be rendered on the client-side, i.e. with JavaScript.
In the context of a progressive enhancement mindset, this makes perfect sense. Our minimum viable experience should work without JavaScript due to the fragility of a JavaScript-dependent approach so should not include script-triggering buttons which might not work. The JavaScript which applies the enhancements should not only listen for and act upon button events, but should also be responsible for actually rendering the button.
This is how I used to build JavaScript interactions as standard, however sadly due to time constraints and framework conventions I don’t always follow this best practice on all projects.
At work, we use Stimulus. Stimulus has a pretty appealing philosophy:
Stimulus is designed to enhance static or server-rendered HTML—the “HTML you already have”
However in their examples they always render buttons on the server; they always assume the JavaScript-powered experience is the baseline experience. I’ve been pondering whether that could easily be adapted toward better progressive enhancement and it seems it can.
My hunch was that I should use the connect()
lifecycle method to render a button
into the component (and introduce any other script-dependent markup adjustments) at the earliest opportunity. I wasn’t sure whether creating new DOM elements at this point and fitting them with Stimulus-related attributes such as action
and target
would make them available via the standard Stimulus APIs like server-rendered elements but was keen to try. I started by checking if anyone was doing anything similar and found a thread where Stimulus contributor Javan suggested that DIY target creation is fine.
I then gave that a try and it worked! Check out my pen Stimulus with true progressive enhancement. It’s a pretty trivial example for now, but proves the concept.