Overview
Laravel Breeze Next.js includes a set of reusable components built with Headless UI and styled with Tailwind CSS.Component Library
Navigation Components
NavLink
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
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>
)
}
ResponsiveNavLink
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
Dropdown Components
Dropdown
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
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>
DropdownLink
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
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
const [status, setStatus] = useState<string>('')
<AuthSessionStatus className="mb-4" status={status} />
ApplicationLogo
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
<ApplicationLogo className="w-20 h-20 fill-current text-gray-500" />
<ApplicationLogo className="h-10 w-auto fill-current text-gray-600" />
Navigation Component
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
Menu (Dropdown)
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>
Menu.Item
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
- Type your props with TypeScript interfaces
- Extend existing types like
LinkPropsorComponentProps<'button'> - Use ReactNode for children that can be any React element
- Spread props with
{...props}for flexibility - Conditional classes with template literals
- Export variants like
DropdownButtonalongside the main component
Next Steps
- Authentication - Use the useAuth hook
- API Integration - Fetch data with SWR