Almost every site needs a menu, and the pattern is pretty consistent. On
large displays, you'll display some full version of the menu on the page at
all times. On small displays, you'll have a button that opens the menu. It's
pretty clear how to do that with JavaScript.
onClick = moveMenu()
. Maybe trigger a fancy animation for your
open/close button. What might be less clear is how to do that without
relying on JS at all.
Here I'm going to focus mostly on making the small-screen version, since the large-screen version has nothing fancy going on. The menu we'll be developing is the example above, which is also the one I use on this site.
How might we create a mobile menu that does use JS? It might look something like this: (Note this is in Svelte, so I can show you the result easily. If you're not familiar with it that's ok, it should still be easy to follow)
<script>
let show = false;
let toggleMenu = () => {
show = !show;
};
</script>
<div id="menu_bar">
<div id="menu" class={show ? "show" : ""}>Contents of the menu</div>
<button on:click={toggleMenu}>Toggle</button>
</div>
<style>
div#menu {
transform: translateY(-100%);
}
div#menu.show {
transform: translateY(0px);
}
</style>
there it is, just toggle the position using transform
when the "toggle"
button is clicked. You can see it in action there. In order to get something
nice, it'll have to be more complicated than that - a taller menu would be cut
off, for example - but that's the general idea.
How do we accomplish that same idea with only CSS? We need a way to keep
track of whether or not the menu is open, and use that information to style
our menu. A good way to store user input in CSS is the
"checkbox"
input type. Using CSS, you can check if a checkbox
is checked using :checked
, and even style elements based on
that information. It makes creating custom checkboxes very straightforwad,
and is what we'll use to make this menu.
<div id="menu_bar">
<label>
<input type="checkbox" />
<div id="menu">Contents of the menu</div>
<p>Toggle</p>
</label>
</div>
<style>
input {
display: none;
}
input:checked ~ div#menu {
transform: translateY(0px);
}
div#menu {
transform: translateY(-100%);
}
</style>
That's the basic idea! You just store the state of the menu in the checkbox, and it's perfectly interactible automatically.
There are some complications with the menu as it stands.
label
-input
pair. That is obviously easy to fix, I left it out here for simplicity. You
could also use the :has
selector instead if you want to try implementing
this a slightly different way, that would allow you to put the menu outside
the labelid
s. Again, I did that for
simplicity/clarityMenu Size
We can fix the menu size by using another div
<script lang="ts">
</script>
<div id="menu_bar">
<label>
<input type="checkbox" />
<div id="clip">
<div id="menu">
<p>Here is a menu</p>
<p>That is tall</p>
<p>And useful now</p>
</div>
</div>
<div id="toggle">Toggle</div>
</label>
</div>
<style>
input {
display: none;
}
input:checked ~ div div#menu {
transform: translateY(0px);
}
label {
position: relative;
}
div#menu {
transform: translateY(-100%);
}
div#clip {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
overflow: hidden;
}
div#toggle {
z-index: 10;
}
</style>
And that's it! The extra div automatically takes the size of its contents,
so it's the perfect size to crop our menu. And, all this works without any
JS! Try disabling JavaScript on this page (you can use a special extension
for that, or if you have uBlock Origin click "More" and </>
). The initial JS example won't work, but all the others do! And, so does
the menu on this page. It's also easy to add optional functionality that
does use JS if you want, like closing the menu if you click outside for
example.
You'll notice the example menu even has a nice animation for the hamburger
button. That is also done entirely in CSS. It works similarly to how the
menu itself does, except it animates SVG line
s. I won't go into
detail here, but if you're interested check out the GitHub repo for this
site
here.
Well, for fun, mostly. But also, because not everyone has JS! Some people are waiting for JS to load because your site is bloated, or they're on a micro-browser, or they have it disabled for security reasons.
:has
like I allude to above is likely
what you would want.