So I read David Bushell’s post on Invoker Commands and it got me inspired! (In short: Invoker Commands API is a way to send events and open dialogs without JS.) I wanted to make a menu like that for my own site. However, I had important principal requirements for it:
This post is a description of a technique I settled on.
So a question I asked David when I planned for this post was quite hard.
How do I detect if JS or Invoker Commands are on, using pure CSS?
This sure has some minor corner-cases.
But the heuristic is: if it supports JS, then it’s one of the major browsers.
And if it’s one of the major browsers, it does support Invoker Commands too.
David did not have a solution, and I don’t blame him. It’s a non-goal for him to make the website fully accessible to noJS people. But my goal is!
So noscript tag reference is quite quirky:
it interprets its content as HTML when JS is off.
And when JS is on, it interprets it as… plaintext.
Which is really weird, but it actually opens a way for detection of noscript.
In short, you have to add an easily identifiable element inside
And then check its presence in CSS with a
Now that we have a way to detect noJS browsers, we can get to writing our HTML and CSS. First, the easy part: creating the long menu:
(I leave the root link out of it, because it should be displayed at all times.)
Then we can create the
The ID of the
This button toggles the
Now the tasty (and slightly frightening) stuff: CSS!
First, we only decide to show the menu button on smaller screens:
Then, we have to deal with the fact that some browsers display the dialog unconditionally:
And, lastly, we have to hide the long list of links when on smaller screens and when Invoker Commands are likely on:
This has a scary selector, true.
But it’s actually
progressively enhanced!
Whenever these pseudo-classes are unsupported, links simply stay displayed.
So noJS browsers and older browsers get this list of links.
That’s basically it!
There are three main problems with this approach:
You can always send me an email via the feedback form below or
contact me elsewhere.
Care about your noJS and mobile users and be kind to people 💙
<noscript>
<span id=noscript></span>
</noscript>
:root:has( #noscript) > .script {
display: none;
}
Making HTML for the Menu and the Dialog
<nav>
<a href="/">Artyom’s Chaotic Blog</a>
<span id=nav-links>
<a href="/rss.xml">(RSS)</a>
<a href="/about.html">About & Contacts</a>
<!-- ... -->
</span>
</nav>
<dialog id=menu-dialog closedby=any>
<form formmethod="dialog">
<button>Close menu</button>
</form>
<ul>
<li> <a href="/">Artyom’s Chaotic Blog</a>
<li> <a href="/rss.xml">(RSS)</a>
<-- ... -->
</ul>
</dialog>
<nav>
<!-- Accessibility essential skiplink, include yours too! -->
<a class=skiplink href=#maincontent>Skip to content</a>
<a href="/">Artyom’s Chaotic Blog</a>
<!-- ... -->
<button id=menu-button commandfor=menu-dialog command=show-modal>Menu</button>
</nav>
Supporting CSS
/* Adjust the width at your convenience */
@media (min-width: 500px) {
#menu-button {
display: none;
}
}
dialog {
/* ... */
display: none;
}
dialog:modal {
display: block;
}
/* Adjust the width at your convenience */
@media (max-width: 500px) {
:root:not(:has( #noscript)) nav #nav-links {
display: none;
}
}
Corner-cases and Problems
The menu button stays visible for all small screens at all times.
Given that there’s no way to detect Invoker Commands support in CSS, I cannot hide it when unsupported.
This is, however, only a problem for browsers that don’t have Invoker Commands support.
Show Me Your Menus!