Skip to main content

Overview

Laravel Breeze Next.js includes a set of reusable components built with Headless UI and styled with Tailwind CSS.

Component Library

A navigation link with active state styling:
src/components/NavLink.tsx
import React, { ReactNode } from 'react'
import Link, { LinkProps } from 'next/link'

interface NavLinkProps extends LinkProps {
  active?: boolean
  children: ReactNode
}

const NavLink = ({
  active = false,
  href,
  children,
  ...props
}: NavLinkProps) => (
  <Link
    as="a"
    href={href}
    {...props}
    className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 focus:outline-hidden transition duration-150 ease-in-out ${
      active
        ? 'border-indigo-400 text-gray-900 focus:border-indigo-700'
        : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300'
    }`}>
    {children}
  </Link>
)

export default NavLink
Usage:
import { usePathname } from 'next/navigation'
import NavLink from '@/components/NavLink'

const Navigation = () => {
  const pathname = usePathname()
  
  return (
    <div className="flex space-x-8">
      <NavLink 
        href="/dashboard" 
        active={pathname === '/dashboard'}
      >
        Dashboard
      </NavLink>
      <NavLink 
        href="/profile" 
        active={pathname === '/profile'}
      >
        Profile
      </NavLink>
    </div>
  )
}
Mobile navigation link with active state:
src/components/ResponsiveNavLink.tsx
import Link, { LinkProps } from 'next/link'
import { ComponentProps, ReactNode } from 'react'

interface ResponsiveNavLinkProps extends LinkProps {
  active?: boolean
  children: ReactNode
}

const ResponsiveNavLink = ({
  active = false,
  children,
  ...props
}: ResponsiveNavLinkProps) => (
  <Link
    {...props}
    className={`block pl-3 pr-4 py-2 border-l-4 text-base font-medium leading-5 focus:outline-hidden transition duration-150 ease-in-out ${
      active
        ? 'border-indigo-400 text-indigo-700 bg-indigo-50 focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700'
        : 'border-transparent text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300'
    }`}>
    {children}
  </Link>
)

export const ResponsiveNavButton = (props: ComponentProps<'button'>) => (
  <button
    className="block w-full pl-3 pr-4 py-2 border-l-4 text-left text-base font-medium leading-5 focus:outline-hidden transition duration-150 ease-in-out border-transparent text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300"
    {...props}
  />
)

export default ResponsiveNavLink
Built with Headless UI’s Menu component:
src/components/Dropdown.tsx
import React, { ReactNode } from 'react'
import { Menu, Transition } from '@headlessui/react'

type DropdownProps = {
  width?: number
  trigger: ReactNode
  children: ReactNode
  contentClasses?: string
  align?: 'right' | 'left' | 'top'
}

const Dropdown = ({
  align = 'right',
  width = 48,
  contentClasses = 'py-1 bg-white',
  trigger,
  children,
}: DropdownProps) => {
  let alignmentClasses: string

  switch (align) {
    case 'left':
      alignmentClasses = 'origin-top-left left-0'
      break
    case 'top':
      alignmentClasses = 'origin-top'
      break
    case 'right':
    default:
      alignmentClasses = 'origin-top-right right-0'
      break
  }

  return (
    <Menu as="div" className="relative">
      {({ open }) => (
        <>
          <Menu.Button as={React.Fragment}>{trigger}</Menu.Button>

          <Transition
            show={open}
            enter="transition ease-out duration-200"
            enterFrom="transform opacity-0 scale-95"
            enterTo="transform opacity-100 scale-100"
            leave="transition ease-in duration-75"
            leaveFrom="transform opacity-100 scale-100"
            leaveTo="transform opacity-0 scale-95">
            <div
              className={`absolute z-50 mt-2 w-${width} rounded-md shadow-lg ${alignmentClasses}`}>
              <Menu.Items
                className={`rounded-md focus:outline-hidden ring-1 ring-black ring-opacity-5 ${contentClasses}`}
                static>
                {children}
              </Menu.Items>
            </div>
          </Transition>
        </>
      )}
    </Menu>
  )
}

export default Dropdown
Usage:
import Dropdown from '@/components/Dropdown'
import { DropdownButton } from '@/components/DropdownLink'

<Dropdown
  align="right"
  width={48}
  trigger={
    <button className="flex items-center text-sm font-medium">
      <div>{user?.name}</div>
      <svg className="ml-1 h-4 w-4">...</svg>
    </button>
  }>
  <DropdownButton onClick={logout}>Logout</DropdownButton>
</Dropdown>
Dropdown menu items:
src/components/DropdownLink.tsx
import { Menu } from '@headlessui/react'
import Link, { LinkProps } from 'next/link'
import { ComponentProps, ReactNode } from 'react'

interface DropdownLinkProps extends LinkProps {
  children: ReactNode
}

interface DropdownButtonProps extends ComponentProps<'button'> {
  children: ReactNode
}

const DropdownLink = ({ children, ...props }: DropdownLinkProps) => (
  <Menu.Item>
    {({ active }) => (
      <Link
        {...props}
        className={`w-full text-left block px-4 py-2 text-sm leading-5 text-gray-700 ${
          active ? 'bg-gray-100' : ''
        } focus:outline-hidden transition duration-150 ease-in-out`}>
        {children}
      </Link>
    )}
  </Menu.Item>
)

export const DropdownButton = ({ children, ...props }: DropdownButtonProps) => (
  <Menu.Item>
    {({ active }) => (
      <button
        className={`w-full text-left block px-4 py-2 text-sm leading-5 text-gray-700 ${
          active ? 'bg-gray-100' : ''
        } focus:outline-hidden transition duration-150 ease-in-out`}
        {...props}>
        {children}
      </button>
    )}
  </Menu.Item>
)

export default DropdownLink

Auth Components

AuthCard

Container for authentication forms:
src/components/AuthCard.tsx
import React, { ReactNode } from 'react'

type Props = {
  logo: ReactNode
  children: ReactNode
}

const AuthCard = ({ logo, children }: Props) => {
  return (
    <div className="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
      <div>{logo}</div>

      <div className="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
        {children}
      </div>
    </div>
  )
}

export default AuthCard
Usage:
import AuthCard from '@/components/AuthCard'
import ApplicationLogo from '@/components/ApplicationLogo'
import Link from 'next/link'

const LoginPage = () => {
  return (
    <AuthCard
      logo={
        <Link href="/">
          <ApplicationLogo className="w-20 h-20 fill-current text-gray-500" />
        </Link>
      }>
      <form>
        {/* Form fields */}
      </form>
    </AuthCard>
  )
}

AuthSessionStatus

Display authentication status messages:
src/components/AuthSessionStatus.tsx
import React, { ComponentProps } from 'react'

interface AuthSessionStatusProps extends ComponentProps<'div'> {
  status: string
}

const AuthSessionStatus = ({
  status,
  className,
  ...props
}: AuthSessionStatusProps) => {
  return (
    <>
      {status && (
        <div
          className={`${className} font-medium text-sm text-green-600`}
          {...props}>
          {status}
        </div>
      )}
    </>
  )
}

export default AuthSessionStatus
Usage:
const [status, setStatus] = useState<string>('')

<AuthSessionStatus className="mb-4" status={status} />
The Laravel logo component:
src/components/ApplicationLogo.tsx
const ApplicationLogo = ({ ...props }) => (
  <svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {...props}>
    <path d="M305.8 81.125C305.77 80.995..." />
  </svg>
)

export default ApplicationLogo
Usage:
<ApplicationLogo className="w-20 h-20 fill-current text-gray-500" />
<ApplicationLogo className="h-10 w-auto fill-current text-gray-600" />
Complete navigation with dropdown menu:
src/components/Layouts/Navigation.tsx
import Link from 'next/link'
import { useState } from 'react'
import { usePathname } from 'next/navigation'

import NavLink from '@/components/NavLink'
import Dropdown from '@/components/Dropdown'
import ResponsiveNavLink, {
  ResponsiveNavButton,
} from '@/components/ResponsiveNavLink'
import { DropdownButton } from '@/components/DropdownLink'
import ApplicationLogo from '@/components/ApplicationLogo'

import { UserType } from '@/types/User'
import { useAuth } from '@/hooks/auth'

const Navigation = ({ user }: { user: UserType }) => {
  const pathname = usePathname()
  const { logout } = useAuth({})
  const [open, setOpen] = useState<boolean>(false)

  return (
    <nav className="bg-white border-b border-gray-100">
      {/* Primary Navigation Menu */}
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex justify-between h-16">
          <div className="flex">
            {/* Logo */}
            <div className="shrink-0 flex items-center">
              <Link href="/dashboard">
                <ApplicationLogo className="block h-10 w-auto fill-current text-gray-600" />
              </Link>
            </div>

            {/* Navigation Links */}
            <div className="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
              <NavLink href="/dashboard" active={pathname === '/dashboard'}>
                Dashboard
              </NavLink>
            </div>
          </div>

          {/* Settings Dropdown */}
          <div className="hidden sm:flex sm:items-center sm:ml-6">
            <Dropdown
              align="right"
              width={48}
              trigger={
                <button className="flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 focus:outline-hidden transition duration-150 ease-in-out">
                  <div>{user?.name}</div>
                  <div className="ml-1">
                    <svg className="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                      <path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
                    </svg>
                  </div>
                </button>
              }>
              <DropdownButton onClick={logout}>Logout</DropdownButton>
            </Dropdown>
          </div>

          {/* Hamburger */}
          <div className="-mr-2 flex items-center sm:hidden">
            <button
              onClick={() => setOpen(open => !open)}
              className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
              <svg className="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
                {open ? (
                  <path className="inline-flex" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
                ) : (
                  <path className="inline-flex" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
                )}
              </svg>
            </button>
          </div>
        </div>
      </div>

      {/* Responsive Navigation Menu */}
      {open && (
        <div className="block sm:hidden">
          <div className="pt-2 pb-3 space-y-1">
            <ResponsiveNavLink href="/dashboard" active={pathname === '/dashboard'}>
              Dashboard
            </ResponsiveNavLink>
          </div>

          <div className="pt-4 pb-1 border-t border-gray-200">
            <div className="flex items-center px-4">
              <div className="shrink-0">
                <svg className="h-10 w-10 fill-current text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
                </svg>
              </div>
              <div className="ml-3">
                <div className="font-medium text-base text-gray-800">{user?.name}</div>
                <div className="font-medium text-sm text-gray-500">{user?.email}</div>
              </div>
            </div>
            <div className="mt-3 space-y-1">
              <ResponsiveNavButton onClick={logout}>Logout</ResponsiveNavButton>
            </div>
          </div>
        </div>
      )}
    </nav>
  )
}

export default Navigation

Headless UI Components

Used in the Dropdown component:
import { Menu, Transition } from '@headlessui/react'

<Menu as="div" className="relative">
  {({ open }) => (
    <>
      <Menu.Button>{trigger}</Menu.Button>
      <Transition show={open} {...transitionProps}>
        <Menu.Items>{children}</Menu.Items>
      </Transition>
    </>
  )}
</Menu>
Provides active state and click handling:
<Menu.Item>
  {({ active }) => (
    <button className={active ? 'bg-gray-100' : ''}>
      Logout
    </button>
  )}
</Menu.Item>

TypeScript Types

User Type

src/types/User.ts
export interface UserType {
  id: number
  email: string
  name: string
  email_verified_at?: Date
  created_at: Date
  updated_at: Date
}

Best Practices

  1. Type your props with TypeScript interfaces
  2. Extend existing types like LinkProps or ComponentProps<'button'>
  3. Use ReactNode for children that can be any React element
  4. Spread props with {...props} for flexibility
  5. Conditional classes with template literals
  6. Export variants like DropdownButton alongside the main component

Next Steps

Build docs developers (and LLMs) love