mdxui

Header Component

Navigation header with logo, links, and CTA buttons

Header Component

The <Header> component provides site navigation with logo, links, and call-to-action buttons. It includes responsive mobile menu, scroll effects, and customizable styling.

Interface

import type { HeaderProps } from 'mdxui'

interface HeaderLink {
  label: string
  href: string
}

interface HeaderProps {
  /** Logo element */
  logo: ReactNode
  /** Navigation links */
  nav: NavItem[]
  /** Primary CTA button */
  cta?: CTA
  /** Custom link component (e.g., Next.js Link) */
  LinkComponent?: ComponentType<HeaderLinkProps>
}

Usage

Basic Example

import { Header } from '@mdxui/beacon'

<Header
  logo={<Logo />}
  nav={[
    { label: "Features", href: "/features" },
    { label: "Pricing", href: "/pricing" },
    { label: "Docs", href: "/docs" },
  ]}
  cta={{ label: "Sign Up", href: "/signup" }}
/>

For Next.js or React Router:

import Link from 'next/link'

<Header
  logo={<Logo />}
  nav={navLinks}
  LinkComponent={Link}
/>

Full Example

<Header
  logo={<Logo src="/logo.svg" alt="Acme" />}
  nav={[
    { label: "Features", href: "#features" },
    { label: "Pricing", href: "#pricing" },
    { label: "Docs", href: "/docs" },
    { label: "Blog", href: "/blog" },
  ]}
  cta={{ label: "Get Started", href: "/signup" }}
/>

Props

logo (required)

Type: ReactNode

Logo element or component displayed on the left side of the header.

// Image logo
<Header logo={<img src="/logo.svg" alt="Acme" />} />

// Component logo
<Header logo={<Logo size="lg" />} />

// Text logo
<Header logo={<span className="font-bold text-xl">Acme</span>} />

Type: NavItem[]

Array of navigation links displayed in the center (desktop) or mobile menu.

interface NavItem {
  label: string
  href: string
}

<Header
  nav={[
    { label: "Features", href: "#features" },
    { label: "Pricing", href: "#pricing" },
    { label: "About", href: "/about" },
  ]}
/>

cta (optional)

Type: CTA

Primary call-to-action button displayed on the right side.

interface CTA {
  label: string
  href: string
  variant?: 'default' | 'outline' | 'ghost'
}

<Header
  cta={{
    label: "Sign Up",
    href: "/signup",
    variant: "default"
  }}
/>

LinkComponent (optional)

Type: ComponentType<HeaderLinkProps>

Custom link component for routing (e.g., Next.js Link, React Router Link).

import Link from 'next/link'

<Header LinkComponent={Link} />

Interface:

interface HeaderLinkProps {
  href: string
  children: ReactNode
  className?: string
  onClick?: () => void
}

Implementation Details

The reference implementation from @mdxui/beacon includes:

Features

  • Responsive Mobile Menu - Hamburger menu for mobile/tablet
  • Scroll Effects - Background blur and border on scroll
  • Sticky Position - Fixed to top of viewport
  • Smooth Animations - CSS transitions for all state changes
  • Body Scroll Lock - Prevents scrolling when mobile menu is open

Structure

<header className="fixed inset-x-0 top-0 z-50">
  <div className="container">
    <div className="flex items-center justify-between">
      {/* Logo */}
      <LinkComponent href="/">
        {logo}
      </LinkComponent>

      {/* Desktop Navigation (center) */}
      <NavigationMenu>
        {nav.map(link => (
          <NavigationMenuItem>
            <LinkComponent href={link.href}>
              {link.label}
            </LinkComponent>
          </NavigationMenuItem>
        ))}
      </NavigationMenu>

      {/* Mobile Menu Button */}
      <button onClick={toggleMobileMenu}>
        <Menu />
      </button>

      {/* Desktop CTA */}
      {cta && (
        <Button variant={cta.variant}>
          {cta.label}
        </Button>
      )}
    </div>

    {/* Mobile Menu */}
    {isMobileMenuOpen && (
      <nav>
        {nav.map(link => (
          <LinkComponent href={link.href}>
            {link.label}
          </LinkComponent>
        ))}
      </nav>
    )}
  </div>
</header>

Scroll Behavior

The header adds a backdrop blur and border on scroll:

const [isScrolled, setIsScrolled] = useState(false)

useEffect(() => {
  const handleScroll = () => {
    setIsScrolled(window.scrollY > 50)
  }
  window.addEventListener('scroll', handleScroll)
  return () => window.removeEventListener('scroll', handleScroll)
}, [])

Styles:

<header
  data-scrolled={isScrolled}
  className="
    in-data-scrolled:bg-background/75
    in-data-scrolled:backdrop-blur
    in-data-scrolled:border-b
  "
>

Variants

With Secondary CTA

Some implementations support both primary and secondary CTAs:

<Header
  logo={<Logo />}
  nav={navLinks}
  primaryCta="Sign Up"
  onPrimaryCta={() => router.push('/signup')}
  secondaryCta="Log In"
  onSecondaryCta={() => router.push('/login')}
/>

With Max Width

Control the container width:

<Header
  logo={<Logo />}
  nav={navLinks}
  maxWidth="max-w-7xl"  // or 'max-w-5xl', 'max-w-6xl'
/>

Custom Styling

Pass additional classes:

<Header
  logo={<Logo />}
  nav={navLinks}
  className="bg-transparent border-none"
/>

Real-World Examples

Marketing Site Header

const marketingNav = [
  { label: "Features", href: "#features" },
  { label: "Pricing", href: "#pricing" },
  { label: "Customers", href: "#customers" },
  { label: "Blog", href: "/blog" },
]

<Header
  logo={<Logo variant="dark" />}
  nav={marketingNav}
  cta={{ label: "Start Free Trial", href: "/signup" }}
/>

Documentation Header

const docsNav = [
  { label: "Docs", href: "/docs" },
  { label: "API", href: "/docs/api" },
  { label: "Examples", href: "/examples" },
  { label: "GitHub", href: "https://github.com/..." },
]

<Header
  logo={<Logo />}
  nav={docsNav}
  cta={{ label: "Dashboard", href: "/app" }}
/>

Minimal Header

<Header
  logo={<Logo />}
  nav={[
    { label: "About", href: "/about" },
    { label: "Contact", href: "/contact" },
  ]}
/>

Accessibility

The reference implementation includes:

  • Semantic HTML - Uses <header>, <nav>, and <button> elements
  • ARIA Labels - Mobile menu toggle has aria-label
  • Keyboard Navigation - Full keyboard support via Radix NavigationMenu
  • Focus Management - Visible focus indicators
  • Screen Reader Support - Proper labeling for assistive tech
<button
  type="button"
  onClick={toggleMenu}
  aria-label={isMobileMenuOpen ? 'Close Menu' : 'Open Menu'}
  aria-expanded={isMobileMenuOpen}
>
  <Menu />
</button>

Composition with Site

The <Header> component is typically rendered by <Site>:

<Site name="Acme">
  {/* Site automatically renders Header */}
  <LandingPage>...</LandingPage>
</Site>

How it works:

  1. The <Site> component includes <Header> automatically
  2. Header props are configured in the template implementation
  3. You can override by passing custom Header component

Mobile Menu Behavior

Open State

When the mobile menu is open:

  • Body scroll is disabled
  • Menu slides in from top
  • Backdrop blur is applied
  • Close icon (X) replaces hamburger icon

Close Triggers

The mobile menu closes when:

  • User clicks the close button
  • User navigates to a link
  • User clicks outside the menu
  • Screen resizes to desktop

Implementation

const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)

// Disable body scroll when menu is open
useEffect(() => {
  if (isMobileMenuOpen) {
    document.body.style.overflow = 'hidden'
  }
  return () => {
    document.body.style.overflow = ''
  }
}, [isMobileMenuOpen])

// Close menu on navigation
const closeMenu = () => setIsMobileMenuOpen(false)

{nav.map(link => (
  <LinkComponent
    href={link.href}
    onClick={closeMenu}
  >
    {link.label}
  </LinkComponent>
))}

TypeScript

Full type definitions:

import type { ReactNode, ComponentType } from 'react'
import type { z } from 'zod'

export const NavItemSchema = z.object({
  label: z.string(),
  href: z.string(),
})

export const CTASchema = z.object({
  label: z.string(),
  href: z.string(),
  variant: z.enum(['default', 'outline', 'ghost']).optional(),
})

export const HeaderPropsSchema = z.object({
  logo: z.any(),
  nav: z.array(NavItemSchema),
  cta: CTASchema.optional(),
  LinkComponent: z.any().optional(),
})

export type NavItem = z.infer<typeof NavItemSchema>
export type CTA = z.infer<typeof CTASchema>
export type HeaderProps = z.infer<typeof HeaderPropsSchema>
export type Header = Component<HeaderProps>

On this page