Navbar
The Navbar component is a versatile and animated navigation bar that supports multiple styles, animations, and layouts. Built with Framer Motion, it offers smooth animations and various interactive features including submenu support and spotlight effects.
- Preview
- Code
<Navbar variant="default" animationType="slide">
<div className="flex gap-4">
<Button>Home</Button>
<Button>About</Button>
<Button>Contact</Button>
</div>
</Navbar>
Installation
- CLI
- Manual
ignix add component navbar
"use client";
import { motion, HTMLMotionProps, AnimatePresence } from "framer-motion";
import * as React from "react";
import { cva, VariantProps } from "class-variance-authority";
import { cn } from "../../../utils/cn";
import { ChevronDown, ChevronUp } from "lucide-react";
export interface NavbarProps
extends Omit<HTMLMotionProps<"nav">, "ref">,
VariantProps<typeof navbarVariants> {
animationType?:
| "slide"
| "glow"
| "basic"
| "spotlight"
| "hoverSubmenu"
| "clickSubmenu";
direction?: "horizontal" | "vertical";
children?: React.ReactNode;
submenuContent?: React.ReactNode;
header?: string;
}
const navbarVariants = cva(
"flex items-center justify-between px-4 py-3 shadow-md transition-all",
{
variants: {
variant: {
default: "bg-background text-foreground",
dark: "bg-card text-card-foreground",
transparent: "bg-transparent text-transparent",
glass: "bg-white/10 backdrop-blur-lg text-[var(--color-navbar-glass-text)]",
gradient:
"bg-gradient-to-r from-[var(--color-navbar-gradient-from)] to-[var(--color-navbar-gradient-to)] text-white",
primary: "bg-primary text-primary-foreground",
},
size: {
sm: "h-12",
md: "h-16",
lg: "h-20",
xl: "h-24",
},
weight: {
light: "font-light",
normal: "font-normal",
medium: "font-medium",
semibold: "font-semibold",
bold: "font-bold",
},
align: {
left: "text-left",
center: "text-center",
right: "text-right",
},
textColor: {
default: "text-foreground",
muted: "text-muted-foreground",
primary: "text-primary",
secondary: "text-secondary",
accent: "text-accent",
white: "text-white",
},
direction: {
horizontal: "flex-row",
vertical: "flex-col items-start space-y-4 p-4",
},
},
defaultVariants: {
weight: "semibold",
align: "left",
direction: "horizontal",
},
}
);
// Improved submenu animation with subtle y translation and opacity fades
const submenuVariants = {
hidden: { opacity: 0, y: -8 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -8 },
};
// Refined animation variants, no infinite loops, smoother springs & easing
const animationVariants: Record<string, Partial<HTMLMotionProps<"nav">>> = {
slide: {
initial: { y: -40, opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: -40, opacity: 0 },
transition: { duration: 0.5, ease: [0.4, 0, 0.2, 1] },
},
glow: {
whileHover: {
boxShadow: "0 0 12px 4px var(--primary)",
transition: { duration: 0.4, ease: "easeInOut" },
},
},
basic: {
initial: { opacity: 1 },
animate: { opacity: 1 },
transition: { duration: 0.3 },
},
spotlight: {
whileHover: {
scale: 1.1,
transition: { duration: 0.3, ease: "easeOut" },
},
},
hoverSubmenu: {
initial: { y: 0, opacity: 1 },
animate: { y: 0, opacity: 1 },
transition: { duration: 0.3 },
},
clickSubmenu: {
initial: { y: 0, opacity: 1 },
animate: { y: 0, opacity: 1 },
transition: { duration: 0.3 },
},
};
const Navbar: React.FC<NavbarProps> = ({
className,
variant,
size,
direction = "horizontal",
children,
animationType = "slide",
submenuContent,
header,
...props
}) => {
const animation = animationVariants[animationType] || {};
const [, setIsHovered] = React.useState(false); // unused but kept for API compatibility
const [hovered, setHovered] = React.useState(false);
direction =
animationType === "hoverSubmenu" || animationType === "clickSubmenu"
? "vertical"
: direction;
const [clicked, setClicked] = React.useState(false);
const handleToggle = (e: React.MouseEvent) => {
// Only toggle if clicking on the header area or chevron
const target = e.target as HTMLElement;
if (
animationType === "clickSubmenu" &&
(target.closest('.navbar-header') || target.closest('.navbar-chevron'))
) {
e.stopPropagation();
setClicked((prev) => !prev);
}
};
const showSubmenu =
(animationType === "hoverSubmenu" && hovered) ||
(animationType === "clickSubmenu" && clicked);
return (
<motion.nav
className={cn(
navbarVariants({ variant, size, direction }),
"[&_input]:text-black [&_input]:border [&_input]:border-gray-300 [&_input]:rounded [&_input]:px-2 [&_input]:py-1 relative",
className,
{
'overflow-visible': showSubmenu,
'z-50': showSubmenu
}
)}
{...animation}
{...props}
onMouseEnter={() => {
setIsHovered(true);
if (animationType === "hoverSubmenu") setHovered(true);
}}
onMouseLeave={() => {
setIsHovered(false);
if (animationType === "hoverSubmenu") setHovered(false);
}}
onClick={handleToggle}
>
{(animationType === "spotlight" || animationType === "basic") &&
React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return (
<motion.div
whileHover={
animationType === "spotlight"
? {
scale: 1.1,
background:
"radial-gradient(circle, rgba(255,255,255,0.2) 0%, rgba(0,0,0,0) 80%)",
transition: { duration: 0.3 },
}
: { scale: 1.05, transition: { duration: 0.3 } }
}
className={
animationType === "spotlight"
? "rounded-full p-3"
: "flex items-center space-x-2 p-3"
}
>
{child}
</motion.div>
);
}
return null;
})}
{(animationType === "hoverSubmenu" ||
animationType === "clickSubmenu") && (
<>
<div className="flex justify-between w-full space-x-2 z-200">
<span className="text-lg font-bold float-left navbar-header cursor-pointer">{header}</span>
{showSubmenu ? (
<span className="float-right navbar-chevron cursor-pointer">
<ChevronUp size={20} />
</span>
) : (
<span className="float-right navbar-chevron cursor-pointer">
<ChevronDown size={20} />
</span>
)}
</div>
<AnimatePresence>
{showSubmenu && (
<motion.div
key="hoverMenu"
initial="hidden"
animate="visible"
exit="exit"
variants={submenuVariants}
transition={{ duration: 0.3, ease: "easeInOut" }}
className={cn(
"absolute left-0 right-0 bg-background w-full px-6 py-4 shadow-lg z-50",
direction === "horizontal" ? "top-full" : "top-0 mt-0",
variant === "dark" ? "bg-card text-card-foreground" : "",
variant === "primary"
? "bg-primary text-primary-foreground"
: ""
)}
>
{submenuContent}
</motion.div>
)}
</AnimatePresence>
</>
)}
{!["hoverSubmenu", "clickSubmenu", "spotlight", "basic"].includes(
animationType
) && children}
</motion.nav>
);
};
Navbar.displayName = "Navbar";
export { Navbar, navbarVariants };
Usage
Import the component:
import { Navbar } from '@ignix-ui/navbar';
Basic Usage
function BasicNavbar() {
return (
<Navbar variant="default" size="md">
<div className="flex gap-4">
<Button>Home</Button>
<Button>About</Button>
<Button>Contact</Button>
</div>
</Navbar>
);
}
Responsive Navigation
Create a responsive navigation bar that adapts to different screen sizes:
import { useState } from 'react';
import { Menu, X } from 'lucide-react';
function ResponsiveNavbar() {
const [isOpen, setIsOpen] = useState(false);
return (
<Navbar variant="default" size="md">
<div className="flex justify-between w-full">
<div className="flex gap-4 items-center">
<Button>Logo</Button>
</div>
{/* Mobile menu button */}
<button
className="md:hidden"
onClick={() => setIsOpen(!isOpen)}
>
{isOpen ? <X /> : <Menu />}
</button>
{/* Desktop menu */}
<div className="hidden md:flex gap-4">
<Button>Home</Button>
<Button>About</Button>
<Button>Contact</Button>
</div>
{/* Mobile menu */}
{isOpen && (
<div className="absolute top-16 left-0 right-0 bg-white md:hidden">
<div className="flex flex-col gap-2 p-4">
<Button>Home</Button>
<Button>About</Button>
<Button>Contact</Button>
</div>
</div>
)}
</div>
</Navbar>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'dark' | 'transparent' | 'glass' | 'gradient' | 'primary' | 'default' | Visual style variant of the navbar |
size | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Height of the navbar |
animationType | 'slide' | 'glow' | 'basic' | 'spotlight' | 'hoverSubmenu' | 'clickSubmenu' | 'slide' | Type of entrance or interaction animation |
direction | 'horizontal' | 'vertical' | 'horizontal' | Layout direction of the navbar items |
header | string | - | Header text, primarily used with submenu variants |
submenuContent | React.ReactNode | - | Content to display in the submenu dropdown |
weight | 'light' | 'normal' | 'medium' | 'semibold' | 'bold' | 'semibold' | Font weight of the navbar text |
align | 'left' | 'center' | 'right' | 'left' | Text alignment within the navbar |
textColor | 'default' | 'muted' | 'primary' | 'secondary' | 'accent' | 'white' | 'default' | Color variant for the navbar text |
className | string | - | Additional CSS classes to apply to the root element |
children | React.ReactNode | - | Content to be displayed inside the navbar |
| All Framer Motion props | HTMLMotionProps<"nav"> | - | Supports standard Framer Motion animation and layout props |