ForgeUI components are designed to be flexible and customizable. Every component accepts className props and uses Tailwind CSS for styling, making it easy to adapt components to your design system.
The cn() utility
ForgeUI uses the cn() utility function to merge Tailwind classes safely. This is defined in lib/utils.ts:
import { clsx , type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn ( ... inputs : ClassValue []) {
return twMerge ( clsx ( inputs ))
}
Why cn()?
The cn() function solves class conflicts by:
clsx - Conditionally constructs className strings
twMerge - Intelligently merges Tailwind classes
// Without cn() - conflicting classes
className = "px-4 px-8" // Both apply, unexpected result
// With cn() - last class wins
cn ( "px-4" , "px-8" ) // "px-8"
Usage in components
Every ForgeUI component uses cn() to merge default styles with user overrides:
import { cn } from "@/lib/utils" ;
export default function ExpandableCard ({ className , items } : ExpandableCardProps ) {
return (
< div className = { cn ( "relative flex items-start p-6" , className ) } >
{ /* content */ }
</ div >
);
}
The user’s className prop overrides any conflicting default classes.
Customizing with className
All ForgeUI components accept a className prop for style overrides.
Basic customization
< TextReveal
text = "Hello World"
className = "text-4xl font-bold text-blue-500"
/>
Responsive design
< AnimatedTabs
tabs = { [ "Home" , "About" , "Contact" ] }
className = "w-full md:w-auto"
/>
Conditional styles
Use cn() for conditional styling:
import { cn } from "@/lib/utils" ;
function Card ({ isHighlighted }) {
return (
< div className = { cn (
"p-4 rounded-lg border" ,
isHighlighted && "border-blue-500 shadow-lg" ,
! isHighlighted && "border-gray-200"
) } >
{ /* content */ }
</ div >
);
}
Component props
ForgeUI components expose TypeScript props for customization.
TextReveal props
type TextRevealProps = {
text : string ; // Text to reveal
className ?: string ; // Custom classes
filter ?: boolean ; // Enable blur effect (default: true)
duration ?: number ; // Animation duration (default: 0.5)
staggerDelay ?: number ; // Delay between words (default: 0.2)
}
< TextReveal
text = "Welcome to ForgeUI"
duration = { 0.8 }
staggerDelay = { 0.3 }
filter = { false }
className = "text-3xl"
/>
AnimatedTabs props
type AnimatedTabsProps = {
tabs : Array < string >; // Tab labels
variant ?: "default" | "underline" ; // Tab style
}
< AnimatedTabs
tabs = {[ "Overview" , "Details" , "Settings" ]}
variant = "underline"
/>
type AnimatedFormProps = {
delay ?: number ; // Animation restart delay (min: 7000)
name ?: string ; // Name to display (default: "Alex Morgan")
}
< AnimatedForm
delay = { 10000 }
name = "John Doe"
/>
TextShimmer props
type TextShimmerProps = {
children : string ; // Text content
as ?: React . ElementType ; // HTML element (default: "p")
className ?: string ; // Custom classes
duration ?: number ; // Animation duration (default: 2)
spread ?: number ; // Shimmer spread (default: 2)
delay ?: number ; // Initial delay (default: 0)
repeatDelay ?: number ; // Delay between repeats (default: 0)
}
< TextShimmer
as = "h1"
duration = { 3 }
spread = { 3 }
className = "text-5xl font-bold"
>
Premium Feature
</ TextShimmer >
ExpandableCard props
export interface CardItem {
id : string ; // Unique identifier
title : string ; // Card title
subtitle : string ; // Card subtitle
icon : React . ReactNode ; // Icon component
description : string ; // Short description
details : string ; // Expanded details
metadata : string ; // Additional metadata
}
export interface ExpandableCardProps {
items : CardItem []; // Array of cards
className ?: string ; // Custom classes
}
const items : CardItem [] = [
{
id: "1" ,
title: "MetaMask" ,
subtitle: "Wallet" ,
icon: < MetaMask className = "h-8 w-8" /> ,
description: "Crypto wallet" ,
details: "Connect and manage your crypto assets" ,
metadata: "Connected" ,
},
];
< ExpandableCard items = { items } className = "max-w-2xl" />
StatsCard props
type StatsCardProps = {
gradientColor ?: string ; // Graph gradient color (default: "#60A5FA")
statsType ?: string ; // Stat label (default: "PRs Merged")
firstPerson ?: string ; // First person name
secondPerson ?: string ; // Second person name
firstData ?: number ; // First person's stat
secondData ?: number ; // Second person's stat
firstImage ?: string ; // First person's image URL
secondImage ?: string ; // Second person's image URL
}
< StatsCard
gradientColor = "#22c55e"
statsType = "Commits"
firstPerson = "Alice"
secondPerson = "Bob"
firstData = { 45 }
secondData = { 38 }
firstImage = "/alice.jpg"
secondImage = "/bob.jpg"
/>
Common customizations
Size adjustments
Width
Height
Padding/Spacing
< AnimatedForm className = "w-[400px]" />
< ExpandableCard className = "max-w-3xl" />
< TextReveal className = "w-full" />
< AnimatedOTP className = "h-64" />
< StatsCard className = "min-h-[500px]" />
< ExpandableCard className = "p-8" />
< AnimatedTabs className = "gap-4" />
Color overrides
// Override text color
< TextReveal
text = "Custom colors"
className = "text-blue-600 dark:text-blue-400"
/>
// Override background
< AnimatedForm className = "[&>div]:bg-gradient-to-br [&>div]:from-blue-500 [&>div]:to-purple-600" />
// Override borders
< ExpandableCard className = "[&_>div]:border-blue-500" />
Use Tailwind’s arbitrary variants like [&>div] to target nested elements.
Typography adjustments
< TextReveal
text = "Large heading"
className = "text-4xl md:text-6xl font-extrabold tracking-tight"
/>
< TextShimmer
className = "font-mono text-xs uppercase tracking-wider"
>
Monospace shimmer
</ TextShimmer >
Border radius
< AnimatedForm className = "[&>div]:rounded-2xl" />
< ExpandableCard className = "[&_button]:rounded-full" />
< StatsCard className = "rounded-3xl" />
Shadows and effects
< ExpandableCard className = "shadow-2xl" />
< AnimatedTabs className = "shadow-lg hover:shadow-xl transition-shadow" />
< StatsCard className = "backdrop-blur-sm" />
Advanced customization
CSS custom properties
Components that use CSS variables can be customized via style prop:
// From text-shimmer.tsx
< TextShimmer
style = { {
'--spread' : '50px' ,
'--base-color' : '#3b82f6' ,
} as React . CSSProperties }
>
Custom shimmer
</ TextShimmer >
Nested element targeting
Use Tailwind’s arbitrary variants to style nested elements:
// Target all buttons inside
className = "[&_button]:bg-blue-500 [&_button]:text-white"
// Target specific children
className = "[&>div>button]:rounded-full"
// Hover states on children
className = "[&_button:hover]:scale-105"
Component composition
Wrap components to add additional functionality:
function CardWithHover ({ children }) {
return (
< div className = "group" >
< ExpandableCard
items = { items }
className = "transition-transform group-hover:scale-102"
/>
< div className = "opacity-0 group-hover:opacity-100 transition-opacity" >
{ children }
</ div >
</ div >
);
}
Overriding animation timing
Many components expose timing props:
< TextReveal
text = "Slower reveal"
duration = { 1.5 } // Slower per-word animation
staggerDelay = { 0.5 } // Longer delay between words
/>
< AnimatedForm
delay = { 15000 } // Longer pause before restart
/>
< AnimatedOTP
delay = { 5000 } // Custom restart interval
/>
Styling patterns
Consistent spacing
ForgeUI uses consistent spacing values:
// Small: 0.5rem (8px)
className = "gap-2 p-2"
// Medium: 1rem (16px)
className = "gap-4 p-4"
// Large: 1.5rem (24px)
className = "gap-6 p-6"
// XL: 2rem (32px)
className = "gap-8 p-8"
Gradient patterns
Common gradient patterns used in ForgeUI:
// Subtle gradient
className = "bg-gradient-to-r from-neutral-50 to-neutral-100
dark : from - neutral - 900 dark : to - neutral - 950 "
// Text gradien t
className = "bg-gradient-to-r from-neutral-700 to-neutral-300
bg - clip - text text - transparent
dark : from - neutral - 400 dark : to - neutral - 700 "
// Border gradient (using box decoration )
className = "bg-gradient-to-r from-blue-500 to-purple-500 p-[1px] rounded-lg"
Responsive utilities
< TextReveal
className = "
text-2xl md:text-4xl lg:text-6xl
max-w-sm md:max-w-2xl lg:max-w-4xl
px-4 md:px-6 lg:px-8
"
/>
Best practices
Always use cn() for merging classes
When building components that accept className, use cn() to merge classes: // Good
< div className = { cn ( "default-class" , className ) } />
// Avoid
< div className = { `default-class ${ className } ` } />
This ensures Tailwind classes merge correctly and later classes override earlier ones.
Maintain semantic color tokens
Use semantic tokens instead of hardcoded colors: // Good
className = "text-foreground bg-background border-border"
// Avoid
className = "text-black bg-white border-gray-300"
This ensures components adapt to theme changes.
Always test components at different breakpoints (mobile, tablet, desktop). Use responsive utilities: className = "w-full md:w-1/2 lg:w-1/3"
When customizing, maintain accessibility features: // Good - maintains contrast
className = "bg-blue-600 text-white"
// Avoid - poor contrast
className = "bg-blue-100 text-blue-200"
Ensure WCAG AA contrast ratios (4.5:1 for normal text).
Use arbitrary values sparingly
Prefer Tailwind’s preset values. Use arbitrary values only when necessary: // Prefer preset
className = "w-96" // 384px
// Use arbitrary when needed
className = "w-[342px]" // Exact design requirement
TypeScript integration
ForgeUI components are fully typed. Use TypeScript to discover available props:
import type { TextShimmerProps } from "@/registry/forgeui/text-shimmer" ;
import type { CardItem , ExpandableCardProps } from "@/registry/forgeui/expandable-card" ;
// Type-safe prop usage
const config : TextShimmerProps = {
children: "Hello" ,
duration: 2 ,
spread: 3 ,
className: "text-4xl" ,
};
< TextShimmer { ... config } />
Real-world examples
Custom card layout
function CustomCard () {
return (
< ExpandableCard
items = { items }
className = "
max-w-4xl mx-auto
p-8
[&_button]:rounded-xl
[&_button]:hover:scale-102
[&_button]:transition-transform
"
/>
);
}
Branded text reveal
function BrandedHero () {
return (
< TextReveal
text = "Build amazing products"
duration = { 0.6 }
staggerDelay = { 0.15 }
className = "
text-5xl md:text-7xl
font-extrabold
tracking-tight
bg-gradient-to-r from-blue-600 to-purple-600
bg-clip-text text-transparent
"
/>
);
}
Themed stats display
function TeamStats () {
return (
< StatsCard
gradientColor = "#10b981"
statsType = "Issues Resolved"
firstPerson = "Sarah"
secondPerson = "Mike"
firstData = { 127 }
secondData = { 98 }
className = "
max-w-md
shadow-2xl
border-2 border-green-500/20
hover:border-green-500/40
transition-colors
"
/>
);
}