Skip to main content
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:
  1. clsx - Conditionally constructs className strings
  2. 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"
/>

AnimatedForm props

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

<AnimatedForm className="w-[400px]" />
<ExpandableCard className="max-w-3xl" />
<TextReveal className="w-full" />

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 gradient
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

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.
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).
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
      "
    />
  );
}

Build docs developers (and LLMs) love