DEV Community

Cover image for Creating a sidebar menu in Svelte
Joshua Nussbaum
Joshua Nussbaum

Posted on • Edited on

Creating a sidebar menu in Svelte

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

Top comments (4)

Collapse
 
rhaddlesey profile image
Collapse
 
subhendupsingh profile image
Subhendu Pratap Singh

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.

Collapse
 
rhaddlesey profile image
Dr Richard Haddlesey

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

Collapse
 
joshnuss profile image
Joshua Nussbaum

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 need position: absolute so the it floats above