A common requirement for web apps is a sidebar menu, at least on mobile, sometimes on desktop too.
Here's how you can roll your own with Svelte:
Component Tree
First, let's think 🤔 about what kind of components we need in our tree:
- Navigation bar
<Navbar/>
for our header- Logo
<Logo/>
with a clickable<svg>
- Menu
<Menu/>
with clickable links - Icon
<Hamburger/>
to trigger the sidebar
- Logo
- Sidebar
<Sidebar/>
that will float above the page - Main area
</Main>
where we can put the page content
Page Layout
Our top level layout will be in App.svelte
. We'll define a boolean flag open
to track when the sidebar is open.
<!-- App.svelte -->
<script>
import Navbar from './Navbar.svelte'
import Sidebar from './Sidebar.svelte'
import Main from './Main.svelte'
let open = false
</script>
<Navbar bind:sidebar={open}/>
<Sidebar bind:open/>
<Main/>
Let's also include Tailwind and some global styles. If you're using Svlete's REPL it can be added with <svelte:head>
. If you're using rollup
or webpack
, obvs. use the npm
package instead.
<svelte:head>
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet"/>
</svelte:head>
<style>
:global(body) {
padding: 0;
}
</style>
Navigation bar
In our navigation, we'll use Tailwind's justify-between
to keep the hamburger and logo on the left, with the menu on the right.
<!-- Navbar.svelte -->
<script>
import Logo from './Logo.svelte'
import Hamburger from './Hamburger.svelte'
import Menu from './Menu.svelte'
export let sidebar = false
</script>
<header class="flex justify-between bg-gray-200 p-2 items-center text-gray-600 border-b-2">
<!-- left side -->
<nav class="flex">
<Hamburger bind:open={sidebar}/>
<Logo/>
</nav>
<!-- right side -->
<Menu/>
</header>
Logo
The logo component is just a simple wrapper around <svg>
<!-- Logo.svelte -->
<a href="/"><svg> .... </svg></a>
Not too much to see here 🙈
Hamburger Icon
The hamburger component is also an <svg>
, but it has a CSS transition that toggles from a 3 line "hamburger" to a 2-line "X" when the menu bar is open.
<!-- Hamburger.svelte -->
<script>
export let open = false
</script>
<!-- defines a CSS class `.open` when `open == true` -->
<button class:open on:click={() => open = !open}>
<!-- svg with 3 lines -->
<svg width=32 height=24>
<line id="top" x1=0 y1=2 x2=32 y2=2/>
<line id="middle" x1=0 y1=12 x2=24 y2=12/>
<line id="bottom" x1=0 y1=22 x2=32 y2=22/>
</svg>
</button>
Then we define some CSS:
svg {
min-height: 24px;
transition: transform 0.3s ease-in-out;
}
svg line {
/* `currentColor` means inherit color from the text color */
stroke: currentColor;
stroke-width: 3;
transition: transform 0.3s ease-in-out
}
/* adjust the Z-index, so that the icon is on top of the sidebar */
button {
z-index: 20;
}
.open svg {
transform: scale(0.7)
}
/* rotate the top line */
.open #top {
transform: translate(6px, 0px) rotate(45deg)
}
/* hide the middle */
.open #middle {
opacity: 0;
}
/* rotate the bottom line */
.open #bottom {
transform: translate(-12px, 9px) rotate(-45deg)
}
Floating Sidebar
The sidebar component is an <aside>
that is offscreen by default left: -100%
, but when open == true
, the class .open
is added, which transitions the sidebar to left: 0
. That makes it slide across the screen.
<script>
export let open = false
</script>
<aside class="absolute w-full h-full bg-gray-200 border-r-2 shadow-lg sm:hidden" class:open>
<nav class="p-12 text-xl">
<a class="block" href="#about">About</a>
<a class="block" href="#contact">Contact</a>
</nav>
</aside>
<style>
aside {
/* offscreen by default */
left: -100%;
transition: left 0.3s ease-in-out
}
.open {
/* slide on screen */
left: 0
}
</style>
Conclusion
So there you have it folks, it's that easy!
A useful addition would be to define the menu tree as a JavaScript object, instead of hardcoding it in both the sidebar and navbar menus.
You can find a fully working example here
Happy hacking ✌
Want more?
If you'd like to learn more about Svelte, check out my short video courses ✨
Top comments (4)
Sub-menu?
sidebar with position absolute will scroll up along with the page if the page is long and goes past the first view port. Making its position fixed will fix this behavior.
How do we then add a submenu? Say, I hover over contact and I want a sub-menu with - phone, email, address etc to fold out from contact?
Thanks
For the Sidebar, you can nest multiple levels of
<nav>
. The menu items that have submenus should use<button>
instead of<a>
. Then when the user clicks the<button>
, toggle the visibility of the child<nav>
.For the Menu component, you can use a similar approach, except instead of clicking to show the submenus, you can can use CSS
:hover
modifier to show the submenu. Also, the submenu will needposition: absolute
so the it floats above