Navbar
<nav>
The primary site navigation. Sits fixed or sticky at the top of the viewport.
Overview
Preview
Basic navbar
html
<nav class="flex h-14 items-center justify-between border-b border-zinc-800 bg-zinc-950/80 px-5 backdrop-blur-md">
<!-- Logo -->
<a href="/" class="flex items-center gap-2.5">
<img src="/logo.svg" alt="Oxide UI" class="h-7 w-7" />
<span class="text-sm font-bold text-zinc-100 tracking-tight">Oxide UI</span>
</a>
<!-- Nav links -->
<div class="hidden items-center gap-1 md:flex">
<a href="/docs" class="rounded-md px-3 py-1.5 text-sm text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100 transition-colors">Docs</a>
<a href="/components" class="rounded-md px-3 py-1.5 text-sm text-zinc-100 bg-zinc-800/60">Components</a>
<a href="/examples" class="rounded-md px-3 py-1.5 text-sm text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100 transition-colors">Examples</a>
</div>
<!-- Actions -->
<div class="flex items-center gap-2">
<a href="/login" class="hidden rounded-md border border-zinc-700 px-3 py-1.5 text-xs font-medium text-zinc-300 hover:bg-zinc-800 transition-colors md:block">
Sign in
</a>
<a href="/get-started" class="rounded-md bg-[#D40C37] px-3 py-1.5 text-xs font-medium text-white hover:bg-[#b50a2f] transition-colors">
Get started
</a>
</div>
</nav>Sticky navbar (Nuxt)
vue
<!-- layouts/default.vue -->
<template>
<div>
<nav class="sticky top-0 z-40 flex h-14 items-center justify-between border-b border-zinc-800 bg-zinc-950/90 px-5 backdrop-blur-md">
<NuxtLink to="/" class="flex items-center gap-2.5">
<span class="text-sm font-bold text-zinc-100">Oxide UI</span>
</NuxtLink>
<!-- ... -->
</nav>
<main>
<slot />
</main>
</div>
</template>With active link detection (Nuxt)
vue
<script setup>
const links = [
{ label: 'Docs', to: '/docs' },
{ label: 'Components', to: '/components' },
{ label: 'Examples', to: '/examples' },
]
</script>
<template>
<NuxtLink
v-for="link in links"
:key="link.to"
:to="link.to"
class="rounded-md px-3 py-1.5 text-sm transition-colors"
active-class="bg-zinc-800/60 text-zinc-100"
inactive-class="text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
>
{{ link.label }}
</NuxtLink>
</template>Mobile menu
vue
<script setup>
import { ref } from 'vue'
const mobileOpen = ref(false)
</script>
<template>
<nav class="...">
<!-- Desktop -->
<div class="hidden md:flex ...">...</div>
<!-- Mobile hamburger -->
<button @click="mobileOpen = !mobileOpen" class="md:hidden rounded-md p-2 text-zinc-400 hover:bg-zinc-800">
<svg class="h-5 w-5" ...>☰</svg>
</button>
</nav>
<!-- Mobile drawer -->
<Transition ...>
<div v-if="mobileOpen" class="fixed inset-0 z-30 md:hidden">
<div class="absolute inset-0 bg-black/50" @click="mobileOpen = false" />
<div class="absolute right-0 top-0 h-full w-72 border-l border-zinc-800 bg-zinc-950 p-6">
<nav class="flex flex-col gap-1">
<NuxtLink to="/docs" @click="mobileOpen = false" class="rounded-md px-3 py-2 text-sm text-zinc-300 hover:bg-zinc-800">Docs</NuxtLink>
</nav>
</div>
</div>
</Transition>
</template>Accessibility notes
- Use
<nav aria-label="Main navigation">to distinguish from other nav elements on the page - The active link should have
aria-current="page" - Mobile menu button needs
aria-expandedandaria-controlspointing to the menu id
Mobile menu with Headless UI Disclosure (recommended)
Using Disclosure gives you automatic aria-expanded, keyboard toggle, and managed open state — without manual ref.
vue
<script setup>
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
const links = [
{ label: 'Docs', to: '/docs' },
{ label: 'Components', to: '/components' },
{ label: 'Examples', to: '/examples' },
]
</script>
<template>
<Disclosure as="nav" v-slot="{ open }" class="sticky top-0 z-40 border-b border-zinc-800 bg-zinc-950/90 backdrop-blur-md">
<div class="flex h-14 items-center justify-between px-5">
<!-- Logo -->
<NuxtLink to="/" class="flex items-center gap-2.5">
<span class="text-sm font-bold text-zinc-100">Oxide UI</span>
</NuxtLink>
<!-- Desktop links -->
<div class="hidden items-center gap-1 md:flex">
<NuxtLink
v-for="link in links" :key="link.to" :to="link.to"
class="rounded-md px-3 py-1.5 text-sm transition-colors"
active-class="bg-zinc-800/60 text-zinc-100"
inactive-class="text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
>
{{ link.label }}
</NuxtLink>
</div>
<!-- Mobile hamburger — DisclosureButton manages aria-expanded automatically -->
<DisclosureButton class="rounded-md p-2 text-zinc-400 hover:bg-zinc-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-zinc-500 md:hidden">
<span class="sr-only">{{ open ? 'Close menu' : 'Open menu' }}</span>
<!-- Animated hamburger → X -->
<svg v-if="!open" class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
<svg v-else class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</DisclosureButton>
</div>
<!-- Mobile panel — DisclosurePanel animates open/closed -->
<Transition
enter-active-class="transition ease-out duration-150"
enter-from-class="opacity-0 -translate-y-1"
enter-to-class="opacity-100 translate-y-0"
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<DisclosurePanel class="border-t border-zinc-800 px-4 py-3 md:hidden">
<nav class="flex flex-col gap-0.5">
<NuxtLink
v-for="link in links" :key="link.to" :to="link.to"
class="rounded-md px-3 py-2 text-sm text-zinc-300 hover:bg-zinc-800 transition-colors"
active-class="bg-zinc-800/60 text-zinc-100"
>
{{ link.label }}
</NuxtLink>
</nav>
</DisclosurePanel>
</Transition>
</Disclosure>
</template>