I was browsing the Apple site, and the navbar caught my attention.

Can I re-build that in Svelte? Yes.

Can I make the mobile icon a bit different? Yes.

Okay, challenge accepted!

The output

Here’s the desktop view

Here’s the mobile view

Here’s the mobile view when the menu is active

The navbar Svelte script logic

<script> import { onMount } from "svelte"; // Show mobile icon and display menu let showMobileMenu = false; // List of navigation items const navItems = [ { label: "logo", href: "#" }, { label: "Item 1", href: "#" }, { label: "Item 2", href: "#" }, { label: "Item 3", href: "#" }, { label: "Item 4", href: "#" }, { label: "Item 5", href: "#" }, { label: "Item 6", href: "#" }, { label: "Item 7", href: "#" } ]; // Mobile menu click event handler const handleMobileIconClick = () => (showMobileMenu = !showMobileMenu); // Media match query handler const mediaQueryHandler = e => { // Reset mobile state if (!e.matches) { showMobileMenu = false; } }; // Attach media query listener on mount hook onMount(() => { const mediaListener = window.matchMedia("(max-width: 767px)"); mediaListener.addListener(mediaQueryHandler); }); </script>

This section of the Svelte component is what handles our state, menu items, and even handlers.

The first thing to note is that we have a Svelte state variable called showMobileMenu .

let showMobileMenu = false; const handleMobileIconClick = () => (showMobileMenu = !showMobileMenu);

This state variable will get used in the markup.

I also have a click handler function called, handleMobilIconClick .

The sole job of that function is to toggle boolean values.

const mediaQueryHandler = e => { // Reset mobile state if (!e.matches) { showMobileMenu = false; } }; onMount(() => { const mediaListener = window.matchMedia("(max-width: 767px)"); mediaListener.addListener(mediaQueryHandler); });

I’m also using matchMedia() on my onMount hook .

matchMedia() is like a CSS media query, but more optimal than a window resize event.

The markup

<nav> <div class="inner"> <div on:click={handleMobileIconClick} class={`mobile-icon${showMobileMenu ? ' active' : ''}`}> <div class="middle-line"></div> </div> <ul class={`navbar-list${showMobileMenu ? ' mobile' : ''}`}> {#each navItems as item} <li> <a href={item.href}>{item.label}</a> </li> {/each} </ul> </div> </nav>

Some key parts to this section is the class conditionals, attaching the handleMobileIconClick function to a click event, and going through a an each loop to render the menu items.

The styles

Here are the styles that make up the navgiation bar, mobile menu icon, and it’s transitions.

nav { background-color: rgba(0, 0, 0, 0.8); font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif; height: 45px; } .inner { max-width: 980px; padding-left: 20px; padding-right: 20px; margin: auto; box-sizing: border-box; display: flex; align-items: center; height: 100%; } .mobile-icon { width: 25px; height: 14px; position: relative; cursor: pointer; } .mobile-icon:after, .mobile-icon:before, .middle-line { content: ""; position: absolute; width: 100%; height: 2px; background-color: #fff; transition: all 0.4s; transform-origin: center; } .mobile-icon:before, .middle-line { top: 0; } .mobile-icon:after, .middle-line { bottom: 0; } .mobile-icon:before { width: 66%; } .mobile-icon:after { width: 33%; } .middle-line { margin: auto; } .mobile-icon:hover:before, .mobile-icon:hover:after, .mobile-icon.active:before, .mobile-icon.active:after, .mobile-icon.active .middle-line { width: 100%; } .mobile-icon.active:before, .mobile-icon.active:after { top: 50%; transform: rotate(-45deg); } .mobile-icon.active .middle-line { transform: rotate(45deg); } .navbar-list { display: none; width: 100%; justify-content: space-between; margin: 0; padding: 0 40px; } .navbar-list.mobile { background-color: rgba(0, 0, 0, 0.8); position: fixed; display: block; height: calc(100% - 45px); bottom: 0; left: 0; } .navbar-list li { list-style-type: none; position: relative; } .navbar-list li:before { content: ""; position: absolute; bottom: 0; left: 0; width: 100%; height: 1px; background-color: #424245; } .navbar-list a { color: #fff; text-decoration: none; display: flex; height: 45px; align-items: center; padding: 0 10px; font-size: 13px; } @media only screen and (min-width: 767px) { .mobile-icon { display: none; } .navbar-list { display: flex; padding: 0; } .navbar-list a { display: inline-flex; } }

Full code

<script> import { onMount } from "svelte"; // Show mobile icon and display menu let showMobileMenu = false; // List of navigation items const navItems = [ { label: "logo", href: "#" }, { label: "Item 1", href: "#" }, { label: "Item 2", href: "#" }, { label: "Item 3", href: "#" }, { label: "Item 4", href: "#" }, { label: "Item 5", href: "#" }, { label: "Item 6", href: "#" }, { label: "Item 7", href: "#" } ]; // Mobile menu click event handler const handleMobileIconClick = () => (showMobileMenu = !showMobileMenu); // Media match query handler const mediaQueryHandler = e => { // Reset mobile state if (!e.matches) { showMobileMenu = false; } }; // Attach media query listener on mount hook onMount(() => { const mediaListener = window.matchMedia("(max-width: 767px)"); mediaListener.addListener(mediaQueryHandler); }); </script> <nav> <div class="inner"> <div on:click={handleMobileIconClick} class={`mobile-icon${showMobileMenu ? ' active' : ''}`}> <div class="middle-line"></div> </div> <ul class={`navbar-list${showMobileMenu ? ' mobile' : ''}`}> {#each navItems as item} <li> <a href={item.href}>{item.label}</a> </li> {/each} </ul> </div> </nav> <style> nav { background-color: rgba(0, 0, 0, 0.8); font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif; height: 45px; } .inner { max-width: 980px; padding-left: 20px; padding-right: 20px; margin: auto; box-sizing: border-box; display: flex; align-items: center; height: 100%; } .mobile-icon { width: 25px; height: 14px; position: relative; cursor: pointer; } .mobile-icon:after, .mobile-icon:before, .middle-line { content: ""; position: absolute; width: 100%; height: 2px; background-color: #fff; transition: all 0.4s; transform-origin: center; } .mobile-icon:before, .middle-line { top: 0; } .mobile-icon:after, .middle-line { bottom: 0; } .mobile-icon:before { width: 66%; } .mobile-icon:after { width: 33%; } .middle-line { margin: auto; } .mobile-icon:hover:before, .mobile-icon:hover:after, .mobile-icon.active:before, .mobile-icon.active:after, .mobile-icon.active .middle-line { width: 100%; } .mobile-icon.active:before, .mobile-icon.active:after { top: 50%; transform: rotate(-45deg); } .mobile-icon.active .middle-line { transform: rotate(45deg); } .navbar-list { display: none; width: 100%; justify-content: space-between; margin: 0; padding: 0 40px; } .navbar-list.mobile { background-color: rgba(0, 0, 0, 0.8); position: fixed; display: block; height: calc(100% - 45px); bottom: 0; left: 0; } .navbar-list li { list-style-type: none; position: relative; } .navbar-list li:before { content: ""; position: absolute; bottom: 0; left: 0; width: 100%; height: 1px; background-color: #424245; } .navbar-list a { color: #fff; text-decoration: none; display: flex; height: 45px; align-items: center; padding: 0 10px; font-size: 13px; } @media only screen and (min-width: 767px) { .mobile-icon { display: none; } .navbar-list { display: flex; padding: 0; } .navbar-list a { display: inline-flex; } } </style>

I like to tweet about Svelte and post helpful code snippets. Follow me there if you would like some too!