Live Demo / Download
Expandable headers that reveal a navigation menu upon click are not something you come across every day. Although it is always a good idea to have menu items visible when possible, there are situations when this is not achievable due to the dense amount of information displayed in the header. In those circumstances, having an expandable header that pops out on click is something worth considering, as it can ultimately improve the UX. We got inspired by Atlas and their menu, so we decided to create our own version. We started from scratch and used Tailwind CSS and Alpine.js to build it. Ready? Let's begin!
Building the HTML structure
For this example, we will start from a very basic structure. We want a header with a logo, a menu, and a button to show the menu. But we want the menu to be hidden at first. So, when you click the button, the menu will show up. Basically, our structure will look like this:
<header>
<!-- Always visible area -->
<div>
<a href="#0">Logo</a>
<button>Menu button</button>
</div>
<!-- Expandable / collapsible menu -->
<nav>Navigation menu</nav>
</header>
Now, let's model this structure to create the component just like you see it in the demo.
Styling the visible area with Tailwind classes
In the always-visible area, we have a logo and a menu button:
<header class="relative before:absolute before:inset-0 before:rounded-lg before:shadow-xl before:shadow-slate-900/5 before:bg-white">
<div class="relative">
<div class="flex items-center px-4">
<a class="focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300" href="#0">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" aria-label="Logo">
<g fill="none" fill-rule="nonzero">
<path fill="#C7D2FE" d="M24.317 7.426a8.537 8.537 0 0 1-2.616 15.262 3.15 3.15 0 0 1-3.042-5.268l2.425-2.426a10.079 10.079 0 0 0 2.951-7.764c.088.064.186.128.282.196Z" />
<path fill="#6366F1" d="M7.425 3.68a8.54 8.54 0 0 1 15.262 2.618 3.149 3.149 0 0 1-5.268 3.046L14.995 6.92a10.122 10.122 0 0 0-7.764-2.95c.062-.097.128-.195.194-.29Z" />
<path fill="#C7D2FE" d="M3.68 20.572A8.54 8.54 0 0 1 6.296 5.31a3.148 3.148 0 0 1 3.05 5.268l-2.424 2.424a10.117 10.117 0 0 0-2.95 7.766c-.098-.064-.196-.128-.294-.196Z" />
<path fill="#6366F1" d="M20.574 24.319A8.54 8.54 0 0 1 5.309 21.7a3.15 3.15 0 0 1 5.27-3.05l2.424 2.424a10.098 10.098 0 0 0 7.764 2.95 9.316 9.316 0 0 1-.193.295Z" />
</g>
</svg>
</a>
<button
type="button"
class="grow flex items-center justify-end h-14 pl-4 focus-visible:outline-none group"
>
<div class="p-1.5 group-focus-visible:ring group-focus-visible:ring-indigo-300">
<svg class="fill-slate-400 shrink-0" width="18" height="18" xmlns="http://www.w3.org/2000/svg">
<rect y="8" width="18" height="2" />
<rect y="8" width="18" height="2" class="transform origin-center rotate-90" />
</svg>
</div>
</button>
</div>
</div>
</header>
The logo is a 32x32 pixel inline SVG within a link. The entire remaining area of the header is covered by a button element, with a "+" icon aligned to the right. We also added some modifiers like focus:
and focus-visible:
(along with the group-focus
variant) to make it look better when navigating with the keyboard.
Implementing the navigation menu
Now, we will proceed with the implementation of the navigation menu. This part is a bit more complex, as it includes 3 columns of elements. On larger screens, the first two columns will take up 75% of the width and contain menu links with an icon, title, and a short description. The last column will fill up the remaining space and will include simple textual links, along with the "Login" and "Request Demo" links positioned at the bottom.
<nav class="grid text-sm text-slate-600 overflow-hidden">
<div class="overflow-hidden before:block before:w-full before:h-px before:bg-gradient-to-r before:from-transparent before:via-slate-200 before:to-transparent">
<div class="px-4 md:px-6 py-8">
<div class="space-y-8 lg:flex lg:space-x-12 lg:space-y-0">
<div class="space-y-8 lg:w-3/4 md:flex md:space-x-6 md:space-y-0">
<div class="md:w-1/2">
<div class="text-xs uppercase font-semibold text-slate-400 mb-3">Products</div>
<ul class="space-y-1">
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="14">
<rect width="3" height="2" fill="#6366F1" rx=".5" />
<rect width="3" height="2" x="13" y="12" fill="#A5B4FC" rx=".5" />
<rect width="7" height="2" x="2" y="4" fill="#A5B4FC" rx=".5" />
<rect width="9" height="2" x="5" y="8" fill="#6366F1" rx=".5" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Analytics</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
<!-- More links -->
</ul>
</div>
<div class="md:w-1/2">
<div class="text-xs uppercase font-semibold text-slate-400 mb-3">Flows</div>
<ul class="space-y-1">
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="14">
<path fill="#A5B4FC" d="M15.5 10H13a5.022 5.022 0 0 1-3.453-1.4l-1.2 1.607A7.065 7.065 0 0 0 12 11.92v1.586a.5.5 0 0 0 .853.349l3-3A.5.5 0 0 0 15.5 10ZM1 4a5.022 5.022 0 0 1 3.453 1.4l1.205-1.61A7.028 7.028 0 0 0 1 2a1 1 0 1 0 0 2Z" />
<path fill="#6366F1" d="M13 4h2.5a.5.5 0 0 0 .354-.853l-3-3A.5.5 0 0 0 12 .5v1.58a7.032 7.032 0 0 0-4.6 2.72L5 8a5.025 5.025 0 0 1-4 2 1 1 0 0 0 0 2 7.034 7.034 0 0 0 5.6-2.8L9 6a5.025 5.025 0 0 1 4-2Z" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Flows</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
<!-- More links -->
</ul>
</div>
</div>
<div class="lg:w-1/4 flex flex-col sm:flex-row lg:flex-col sm:justify-between space-y-8 relative before:hidden lg:before:block before:absolute before:-left-6 before:w-px before:h-full before:bg-gradient-to-b before:from-transparent before:via-slate-200 before:to-transparent">
<div>
<div class="text-xs uppercase font-semibold text-slate-400 mb-5">Company</div>
<ul class="space-y-1 -mx-3">
<li>
<a class="flex text-sm font-semibold text-slate-800 px-3 py-1 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Documentation</a>
</li>
<!-- More links -->
</ul>
</div>
<div class="lg:grow flex items-end lg:justify-end">
<div class="flex items-center space-x-3">
<a class="text-sm font-medium text-slate-500 hover:text-slate-900 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Login</a>
<a class="inline-flex justify-center whitespace-nowrap rounded-full bg-white border border-slate-200 px-3 py-1.5 text-sm font-medium text-indigo-500 hover:bg-indigo-600 hover:text-white hover:border-indigo-500 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Request Demo</a>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
This menu can't be collapsed yet, but let's focus on the layout for now. We can figure out how to hide it later on. The most interesting things to note here are:
- The use of
before
pseudo-element for the gradient border at the top. - The use of CSS grid which - as we saw in the tutorial about building a simple accordion component - enables us to animate the height of the navigation menu without relying on JavaScript.
- The use of Tailwind's responsive utility variants to position the menu items effectively on both mobile and desktop screens.
So, we're almost done with the structure. All we have left to do is hide the navigation menu and handle its collapsed / expanded states.
Toggling the header with Alpine.js
If you've been following our Tailwind tutorials, you're already familiar with the incredibly useful Alpine.js library. We love Alpine because it lets us add interactivity to our components without having to write any JavaScript code. In this case, we'll be using it to manage the expansion and contraction of our navigation menu. We'll begin by creating a variable called expanded
and we'll change its state when the button is clicked. Initially, the value of expanded
is set to false
, which means the menu is hidden. But when the button is clicked, the value will toggle to the opposite of its current state (expanded = !expanded
). This will make the menu expand or collapse depending on its current state. To implement this functionality, we'll need to make some integrations to the <header>
and <button>
elements:
<header
x-data="{ expanded: false }"
class="relative before:absolute before:inset-0 before:rounded-lg before:shadow-xl before:shadow-slate-900/5 before:bg-white"
>
<!-- ... -->
<button
type="button"
class="grow flex items-center justify-end h-14 pl-4 focus-visible:outline-none group"
@click="expanded = !expanded"
><!-- ... --></button>
<!-- ... -->
</header>
Now we can add the missing classes to the menu based on the value of the expanded
variable. We'll do this using the Alpine.js x-bind:class
directive, or the shortened version :class
. Let's start from the menu:
<nav
class="grid text-sm text-slate-600 overflow-hidden transition-all duration-500 ease-[cubic-bezier(.5,.85,.25,1.8)] [&[x-cloak]]:hidden"
:class="expanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0 invisible'"
x-cloak
>
So basically, if the expanded
variable is true
(grid-rows-[1fr] opacity-100
), the menu will show up. Viceversa, if it's set to false
, the menu will be hidden (grid-rows-[0fr] opacity-0 invisible
). We also ensured that the menu expands with an easing animation by adding some a few more classes: transition-all
, duration-500
and ease-[cubic-bezier(.5,.85,.25,1.8)]
. Finally, to avoid any glitches before Alpine.js is loaded, we added the x-cloak
attribute. Now, let's move on to managing the rotation of the "+" icon.
<svg class="fill-slate-400 shrink-0 transition duration-300 ease-in-out" :class="{'rotate-[135deg]': expanded}" width="18" height="18" xmlns="http://www.w3.org/2000/svg">
<rect y="8" width="18" height="2" />
<rect y="8" width="18" height="2" class="transform origin-center rotate-90" />
</svg>
When we set the value of expanded
to true
, the icon will rotate 135 degrees with a 300-millisecond transition. Finally, we want the menu to have a bouncy and elastic effect when it is opened and closed. To do this, we will add a few more classes to the <header>
element:
<header
x-data="{ expanded: false }"
class="relative before:absolute before:inset-0 before:rounded-lg before:shadow-xl before:shadow-slate-900/5 before:bg-white before:transition-all before:duration-500 before:ease-[cubic-bezier(.5,.85,.25,1.8)]"
:class="expanded ? 'before:-inset-2 before:top-0' : 'before:inset-0'"
>
Since the header background is defined by the before
pseudo-element, we can easily extend its dimensions using the before:inset-*
class. So basically, what we're doing is making the element 8 pixels bigger when the menu is open. And we're adding this cool elastic effect to the animation with the custom class ease-[cubic-bezier(.5,.85,.25,1.8)]
. Cool! The header component is now complete, but it still doesn't meet accessibility requirements. Let's fix that.
Making the header component fully accessible
Accessibility isn't something we can just ignore nowadays. Making our web pages accessible is a responsibility, not a choice. We want everyone, including those using assistive devices, to be able to access our content. So, to make our component accessible, we need to add the right ARIA attributes to the code. First, we'll give the <button>
element an id
. Then, we'll add the aria-expanded
and aria-controls="menu"
attributes. This will help with the accessibility of the element. In this way:
<button
id="menubutton"
type="button"
class="grow flex items-center justify-end h-14 pl-4 focus-visible:outline-none group"
@click="expanded = !expanded"
:aria-expanded="expanded"
aria-controls="menu"
>
The nav element, instead, will be integrated with an id
that corresponds to the aria-controls
attribute we defined earlier. We'll also add an attribute that specifies the navigation role
and an aria-labelledby
attribute that corresponds to the button's id
.
<nav
id="menu"
role="navigation"
aria-labelledby="menubutton"
class="grid text-sm text-slate-600 overflow-hidden transition-all duration-500 ease-[cubic-bezier(.5,.85,.25,1.8)] [&[x-cloak]]:hidden"
:class="expanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0 invisible'"
x-cloak
>
This way, we've linked the <button>
and <nav>
elements so that the screen reader can tell that the button controls the opening and closing of the menu. And here's what the finished component looks like:
<header
x-data="{ expanded: false }"
class="relative before:absolute before:inset-0 before:rounded-lg before:shadow-xl before:shadow-slate-900/5 before:bg-white before:transition-all before:duration-500 before:ease-[cubic-bezier(.5,.85,.25,1.8)]"
:class="expanded ? 'before:-inset-2 before:top-0' : 'before:inset-0'"
>
<div class="relative">
<div class="flex items-center px-4">
<a class="focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300" href="#0">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" aria-label="Logo">
<g fill="none" fill-rule="nonzero">
<path fill="#C7D2FE" d="M24.317 7.426a8.537 8.537 0 0 1-2.616 15.262 3.15 3.15 0 0 1-3.042-5.268l2.425-2.426a10.079 10.079 0 0 0 2.951-7.764c.088.064.186.128.282.196Z" />
<path fill="#6366F1" d="M7.425 3.68a8.54 8.54 0 0 1 15.262 2.618 3.149 3.149 0 0 1-5.268 3.046L14.995 6.92a10.122 10.122 0 0 0-7.764-2.95c.062-.097.128-.195.194-.29Z" />
<path fill="#C7D2FE" d="M3.68 20.572A8.54 8.54 0 0 1 6.296 5.31a3.148 3.148 0 0 1 3.05 5.268l-2.424 2.424a10.117 10.117 0 0 0-2.95 7.766c-.098-.064-.196-.128-.294-.196Z" />
<path fill="#6366F1" d="M20.574 24.319A8.54 8.54 0 0 1 5.309 21.7a3.15 3.15 0 0 1 5.27-3.05l2.424 2.424a10.098 10.098 0 0 0 7.764 2.95 9.316 9.316 0 0 1-.193.295Z" />
</g>
</svg>
</a>
<button
id="menubutton"
type="button"
class="grow flex items-center justify-end h-14 pl-4 focus-visible:outline-none group"
@click="expanded = !expanded"
:aria-expanded="expanded"
aria-controls="menu"
>
<div class="p-1.5 group-focus-visible:ring group-focus-visible:ring-indigo-300">
<svg class="fill-slate-400 shrink-0 transition duration-300 ease-in-out" :class="{'rotate-[135deg]': expanded}" width="18" height="18" xmlns="http://www.w3.org/2000/svg">
<rect y="8" width="18" height="2" />
<rect y="8" width="18" height="2" class="transform origin-center rotate-90" />
</svg>
</div>
</button>
</div>
<nav
id="menu"
role="navigation"
aria-labelledby="menubutton"
class="grid text-sm text-slate-600 overflow-hidden transition-all duration-500 ease-[cubic-bezier(.5,.85,.25,1.8)] [&[x-cloak]]:hidden"
:class="expanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0 invisible'"
x-cloak
>
<div class="overflow-hidden before:block before:w-full before:h-px before:bg-gradient-to-r before:from-transparent before:via-slate-200 before:to-transparent">
<div class="px-4 md:px-6 py-8">
<div class="space-y-8 lg:flex lg:space-x-12 lg:space-y-0">
<div class="space-y-8 lg:w-3/4 md:flex md:space-x-6 md:space-y-0">
<div class="md:w-1/2">
<div class="text-xs uppercase font-semibold text-slate-400 mb-3">Products</div>
<ul class="space-y-1">
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="14">
<rect width="3" height="2" fill="#6366F1" rx=".5" />
<rect width="3" height="2" x="13" y="12" fill="#A5B4FC" rx=".5" />
<rect width="7" height="2" x="2" y="4" fill="#A5B4FC" rx=".5" />
<rect width="9" height="2" x="5" y="8" fill="#6366F1" rx=".5" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Analytics</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path fill="#6366F1" d="M4 2h3v7a1 1 0 1 0 2 0V2h3a1 1 0 1 0 0-2H4a1 1 0 1 0 0 2Z" />
<path fill="#A5B4FC" d="M12.82 10.116a.5.5 0 0 0-.82.385v1.5H4v-1.5a.5.5 0 0 0-.82-.385l-3 2.5a.5.5 0 0 0 0 .768l3 2.5a.502.502 0 0 0 .532.069A.5.5 0 0 0 4 15.5V14h8v1.5a.5.5 0 0 0 .82.384l3-2.5a.5.5 0 0 0 0-.768l-3-2.5Z" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Transform</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path fill="#A5B4FC" d="M10.742 5.26a4.475 4.475 0 0 0-.825-.64L8.435 6.102a2.531 2.531 0 0 1 .893 4.158l-3 3a2.536 2.536 0 0 1-3.586-3.586l.058-.062a6 6 0 0 1-.37-2.075c0-.134.01-.266.02-.4L1.327 8.26a4.535 4.535 0 0 0 6.414 6.414l3-3a4.536 4.536 0 0 0 0-6.414Z" />
<path fill="#6366F1" d="M5.26 10.742c.25.245.527.46.825.64l1.482-1.48a2.531 2.531 0 0 1-.893-4.16l3-3a2.536 2.536 0 0 1 3.586 3.586l-.062.062c.247.664.374 1.367.375 2.075 0 .134-.01.266-.02.4l1.121-1.123A4.535 4.535 0 0 0 8.26 1.328l-3 3a4.536 4.536 0 0 0 0 6.414Z" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Security</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="12">
<circle cx="8" cy="10.5" r="1.5" fill="#6366F1" />
<path fill="#A5B4FC" d="M11.5 8C10.6 7 9.3 6.5 8 6.5 6.7 6.5 5.4 7 4.5 8L3.1 6.6c1.3-1.4 3-2.1 4.9-2.1 1.9 0 3.6.7 4.9 2.1L11.5 8Z" />
<path fill="#6366F1" d="M8 0C5 0 2.2 1.1 0 3.2l1.4 1.4C3.2 2.9 5.5 2 8 2s4.8.9 6.6 2.7L16 3.2C13.8 1.1 11 0 8 0Z" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Connect</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
</ul>
</div>
<div class="md:w-1/2">
<div class="text-xs uppercase font-semibold text-slate-400 mb-3">Flows</div>
<ul class="space-y-1">
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="14">
<path fill="#A5B4FC" d="M15.5 10H13a5.022 5.022 0 0 1-3.453-1.4l-1.2 1.607A7.065 7.065 0 0 0 12 11.92v1.586a.5.5 0 0 0 .853.349l3-3A.5.5 0 0 0 15.5 10ZM1 4a5.022 5.022 0 0 1 3.453 1.4l1.205-1.61A7.028 7.028 0 0 0 1 2a1 1 0 1 0 0 2Z" />
<path fill="#6366F1" d="M13 4h2.5a.5.5 0 0 0 .354-.853l-3-3A.5.5 0 0 0 12 .5v1.58a7.032 7.032 0 0 0-4.6 2.72L5 8a5.025 5.025 0 0 1-4 2 1 1 0 0 0 0 2 7.034 7.034 0 0 0 5.6-2.8L9 6a5.025 5.025 0 0 1 4-2Z" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Flows</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path fill="#A5B4FC" d="M4.086 5.5 5.5 4.087l6.414 6.414-1.414 1.415z" />
<circle cx="2" cy="2" r="2" fill="#6366F1" />
<path fill="#6366F1" d="M12 12h4v4h-4z" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Tracking</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="12">
<path fill="#6366F1" d="m6.7 5.3-5-5C1.3-.1.7-.1.3.3c-.4.4-.4 1 0 1.4L4.6 6 .3 10.3c-.4.4-.4 1 0 1.4.2.2.4.3.7.3.3 0 .5-.1.7-.3l5-5c.4-.4.4-1 0-1.4Z" />
<path fill="#A5B4FC" d="M7 10h7v2H7z" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Management</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
<li>
<a class="flex space-x-4 px-3 py-2.5 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">
<div class="shrink-0 flex items-center justify-center h-9 w-9 rounded bg-white border border-slate-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="14">
<rect width="3" height="2" fill="#6366F1" rx=".5" />
<rect width="3" height="2" x="13" y="12" fill="#A5B4FC" rx=".5" />
<rect width="7" height="2" x="2" y="4" fill="#A5B4FC" rx=".5" />
<rect width="9" height="2" x="5" y="8" fill="#6366F1" rx=".5" />
</svg>
</div>
<div>
<div class="text-sm font-semibold text-slate-800 mb-0.5">Billing</div>
<div class="text-slate-500">Everything counts. And everything that counts — shapes us.</div>
</div>
</a>
</li>
</ul>
</div>
</div>
<div class="lg:w-1/4 flex flex-col sm:flex-row lg:flex-col sm:justify-between space-y-8 relative before:hidden lg:before:block before:absolute before:-left-6 before:w-px before:h-full before:bg-gradient-to-b before:from-transparent before:via-slate-200 before:to-transparent">
<div>
<div class="text-xs uppercase font-semibold text-slate-400 mb-5">Company</div>
<ul class="space-y-1 -mx-3">
<li>
<a class="flex text-sm font-semibold text-slate-800 px-3 py-1 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Documentation</a>
</li>
<li>
<a class="flex text-sm font-semibold text-slate-800 px-3 py-1 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Pricing</a>
</li>
<li>
<a class="flex text-sm font-semibold text-slate-800 px-3 py-1 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Priority Support</a>
</li>
<li>
<a class="flex text-sm font-semibold text-slate-800 px-3 py-1 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">About</a>
</li>
<li>
<a class="flex text-sm font-semibold text-slate-800 px-3 py-1 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Video Tutorials</a>
</li>
<li>
<a class="flex text-sm font-semibold text-slate-800 px-3 py-1 rounded hover:bg-slate-50 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Community <span class="text-xs italic text-emerald-500 font-normal ml-1">New</span></a>
</li>
</ul>
</div>
<div class="lg:grow flex items-end lg:justify-end">
<div class="flex items-center space-x-3">
<a class="text-sm font-medium text-slate-500 hover:text-slate-900 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Login</a>
<a class="inline-flex justify-center whitespace-nowrap rounded-full bg-white border border-slate-200 px-3 py-1.5 text-sm font-medium text-indigo-500 hover:bg-indigo-600 hover:text-white hover:border-indigo-500 focus-visible:outline-none focus-visible:ring focus-visible:ring-indigo-300 transition" href="#0">Request Demo</a>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</div>
</header>
Conclusions
In this tutorial, we saw how easy it is to create an expandable header component using Alpine.js - without writing a single line of JavaScript, and using a CSS-only approach for animating the menu height. Hope you enjoyed this tutorial, and let us know if you want to see this component integrated into one our Tailwind templates!
Top comments (0)