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 ✨