Creating a sidebar menu in Svelte

Joshua Nussbaum - Feb 29 '20 - - Dev Community

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
  • 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/>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Logo

The logo component is just a simple wrapper around <svg>

<!-- Logo.svelte -->
<a href="/"><svg> .... </svg></a>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

. . . . . . . . . . . . . . . . . . .