🔥 Oxide UI v0.1.0 — Dark mode only, copy-paste ready. Get started →
Skip to content

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>
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>
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-expanded and aria-controls pointing to the menu id

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>

Released under the MIT License.