What is HeroUI? HeroUI Tutorial for Beginners

Mark Ponomarev

Mark Ponomarev

12 June 2025

What is HeroUI? HeroUI Tutorial for Beginners
💡
Want a great API Testing tool that generates beautiful API Documentation?

Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?

Apidog delivers all your demands, and replaces Postman at a much more affordable price!
button

For frontend devs, the demand for aesthetically pleasing, highly performant, and deeply customizable user interfaces has never been greater. Developers are constantly searching for tools that can accelerate their workflow without sacrificing quality or creative control. While monolithic UI component libraries have served the community well for years, a new paradigm is emerging—one that prioritizes developer ownership, modularity, and seamless integration with modern frameworks. It is within this new paradigm that HeroUI carves its niche.

HeroUI is not just another component library; it is a meticulously crafted collection of reusable UI components designed to be integrated directly into your projects. It champions a philosophy that gives developers ultimate control over their codebase. Instead of importing opaque components from a node module, you use the HeroUI Command Line Interface (CLI) to add the actual source code of the components you need into your project. This "copy-and-paste" evolution means every button, card, and dialog box becomes a part of your own application, ready to be tweaked, restyled, and adapted to your specific needs.

Built on the shoulders of giants like React, Tailwind CSS, and Next.js, HeroUI provides the building blocks for creating beautiful, accessible, and responsive user interfaces. It is designed for the modern developer who values both speed and specificity, offering a robust starting point that doesn't lock you into a rigid design system. This article will serve as a comprehensive guide to understanding the core principles of HeroUI, installing it in your project, customizing its look and feel, and leveraging its powerful CLI to build the next generation of web applications.

Part 1: Deconstructing HeroUI - Philosophy and Core Features

Before diving into the technical specifics of installation and usage, it is crucial to understand the "why" behind HeroUI. What problems does it solve, and what makes it a compelling choice in a crowded field of UI tools?

The Philosophy: Ownership and Uncompromised Customization

The fundamental difference between HeroUI and traditional UI libraries like Material-UI or Ant Design lies in the concept of ownership. When you install a traditional library, you are adding a dependency to your package.json. Your application then imports pre-compiled components from this package. While this is convenient, it comes with several drawbacks:

  1. Limited Customization: Styling overrides can be complex, often requiring you to fight against the library's default styles with !important tags or convoluted theme provider configurations.
  2. Black Box Components: The internal logic of the components is hidden away in the node_modules folder. Debugging unexpected behavior or understanding the inner workings becomes significantly harder.
  3. Bundle Size Bloat: You often import the entire library, or at least a significant portion of it, even if you only use a handful of components, potentially increasing your application's final bundle size.
  4. Dependency Hell: You are beholden to the library's update cycle and its dependencies. A breaking change in the library can force a major refactor in your application.

HeroUI sidesteps these issues entirely. By having the CLI place the component's source code directly into your project's directory (e.g., /components/ui), it empowers you in several key ways:

This philosophy is aimed at developers and teams who want to build a unique design system for their product without starting from scratch. It provides the foundational, unstyled (or lightly styled) primitives, and you provide the brand identity.

Key Features at a Glance

HeroUI is more than just its installation method. It comes packed with features designed for a modern development workflow.

Who is HeroUI For?

HeroUI is an ideal choice for a specific type of developer and project:

It may be less suitable for absolute beginners who prefer a more "out-of-the-box," batteries-included solution where minimal configuration is required. The power of HeroUI lies in its configurability, which requires a foundational understanding of Tailwind CSS and the modern front-end development environment.

Part 2: Getting Started - A Detailed Walkthrough of Installation and Setup

Now that we understand the philosophy, let's get our hands dirty. This section provides a meticulous, step-by-step guide to integrating HeroUI into a new or existing project. The recommended and most efficient method is using the official HeroUI CLI.

Prerequisites

Before we begin, ensure your development environment meets the following requirements:

npx create-next-app@latest my-heroui-app

During the Next.js setup, it is recommended to choose TypeScript and Tailwind CSS, as these are foundational to the HeroUI ecosystem.

The HeroUI CLI init Command: Your Starting Point

The init command is the magical entry point into the HeroUI world. It intelligently inspects your project, asks you a series of questions, and then automatically configures everything you need.

Navigate into your project directory:Bash

cd my-heroui-app

Now, run the initialization command:Bash

npx heroui-cli@latest init

The CLI will now guide you through the setup process. Let's break down each question it asks and what your choices mean.

1. "Which style would you like to use?"

2. "Which color would you like to use as a base color?"

3. "Where is your global CSS file?"

4. "Do you want to use CSS variables for colors?"

5. "Where is your tailwind.config.js file?"

6. "Configure import alias for components:"

7. "Configure import alias for utils:"

8. "Are you using React Server Components?"

Once you have answered all the questions, the CLI will perform its magic. It will:

Your project is now fully configured and ready for HeroUI.

Anatomy of the Changes

Let's look closer at the key files the CLI has modified or created.

components.json

This file is the manifest for HeroUI within your project. It stores the choices you made during the init process and tells the CLI how your project is configured.JSON

{
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "app/globals.css",
    "baseColor": "slate",
    "cssVariables": true
  },
  "aliases": {
    "utils": "@/lib/utils",
    "components": "@/components"
  }
}

You should rarely need to edit this file manually, but it's helpful to understand its purpose. It's the brain behind the CLI's operations.

tailwind.config.js

Your Tailwind config will be extended to look something like this. The key additions are the theme extensions and the tailwindcss-animate plugin.JavaScript

/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        // ... and many more color definitions linked to CSS variables
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        // ...
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      keyframes: {
        // ... keyframes for animations
      },
      animation: {
        // ... animation utilities
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
}

Notice how colors like primary are not defined with a hex code but with hsl(var(--primary)). This tells Tailwind to use the CSS variable named --primary, which is defined in your global CSS.

app/globals.css

This file is now the heart of your design system's theme. It will contain the base Tailwind directives and a large block of CSS variables.CSS

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    /* ... many more variables for the light theme */
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    /* ... many more variables for the dark theme */
  }
}

Here you can see the power of this setup. All your light theme colors are defined in the :root scope, and all your dark theme colors are defined inside the .dark class scope. When the .dark class is added to the <html> element, the browser will automatically use the dark theme variables.

Part 3: Mastering Customization - Theming, Layout, and Dark Mode

With HeroUI initialized, the real fun begins: making it your own. The architecture is explicitly designed for deep and intuitive customization.

The Art of Theming with CSS Variables

Theming in HeroUI is a departure from the complex JavaScript-based theme objects you might find in other libraries. It's simpler, more powerful, and leverages modern CSS features. The entire theme—colors, border radii, fonts—is controlled by CSS variables defined in your globals.css file.

Changing Colors

Let's say you want to change your primary brand color. You don't need to go into the Tailwind config. You simply find the relevant CSS variables in globals.css and change their values.

The colors are defined using HSL (Hue, Saturation, Lightness) values, but without the hsl() wrapper. For example:CSS

:root {
  /* ... */
  --primary: 221.2 83.2% 53.3%;
  --primary-foreground: 210 40% 98%;
  /* ... */
}

.dark {
  /* ... */
  --primary: 217.2 91.2% 59.8%;
  --primary-foreground: 210 40% 98%;
  /* ... */
}

To change your primary color to a vibrant green, you could use an online color picker to find the HSL values for your chosen shade and update the variables:CSS

/* In globals.css */
:root {
  /* ... */
  --primary: 142.1 76.2% 36.3%; /* New Green Primary Color */
  --primary-foreground: 355.7 100% 97.3%; /* A contrasting light color for text on the primary color */
  /* ... */
}

.dark {
  /* ... */
  --primary: 142.1 70.2% 46.3%; /* A slightly different green for dark mode */
  --primary-foreground: 355.7 100% 97.3%;
  /* ... */
}

Once you save this file, every component that uses the "primary" color (like <Button>) will instantly update across your entire application to reflect this new green color. This is incredibly powerful.

Changing Border Radius

The roundness of corners on components like cards and inputs is controlled by a single CSS variable: --radius.CSS

/* In globals.css */
:root {
  /* ... */
  --radius: 0.5rem; /* The default value */
}

If you prefer a sharper, more squared-off look, you can reduce this value:CSS

:root {
  --radius: 0.25rem; /* Less rounded */
}

Or, for a very soft, rounded aesthetic, you can increase it:CSS

:root {
  --radius: 1.5rem; /* Very rounded */
}

This single line change will cascade through all your components, ensuring a consistent border radius across your entire UI.

Adding New Colors

You are not limited to the colors provided by the init command. You can easily add your own semantic colors. For instance, let's add a "special" brand color.

Define the CSS variables in globals.css:CSS

/* In globals.css */
:root {
  /* ... */
  --special: 320 86% 59%;
  --special-foreground: 330 100% 98%;
}
.dark {
  /* ... */
  --special: 320 80% 69%;
  --special-foreground: 330 100% 98%;
}

Expose them to Tailwind in tailwind.config.js:JavaScript

// In tailwind.config.js
// ...
extend: {
  colors: {
    // ...
    special: {
      DEFAULT: "hsl(var(--special))",
      foreground: "hsl(var(--special-foreground))",
    },
  },
},
// ...

Now you can use these colors in your components with Tailwind's utility classes, such as bg-special and text-special-foreground.

Building Responsive Layouts

HeroUI components are built with Tailwind CSS, which means they are inherently responsive. You can use Tailwind's responsive prefixes (sm:, md:, lg:, xl:) on any utility class to change a component's style at different screen sizes.

Let's imagine building a simple page layout with a sidebar that is visible on desktop but collapses on mobile. While HeroUI provides the low-level components (Card, Button), you are responsible for composing them into a larger layout.

Here's an example of how you might structure this in a Next.js page component:TypeScript

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

export default function DashboardPage() {
  return (
    <div className="flex min-h-screen flex-col md:flex-row">
      {/* Sidebar */}
      <aside className="w-full border-b bg-muted p-4 md:w-64 md:border-b-0 md:border-r">
        <h2 className="text-lg font-semibold">Navigation</h2>
        <nav className="mt-4 flex flex-row space-x-2 md:flex-col md:space-x-0 md:space-y-2">
          <Button variant="ghost" className="justify-start">Dashboard</Button>
          <Button variant="ghost" className="justify-start">Settings</Button>
          <Button variant="ghost" className="justify-start">Profile</Button>
        </nav>
      </aside>

      {/* Main Content */}
      <main className="flex-1 p-8">
        <h1 className="text-4xl font-bold tracking-tight">Dashboard</h1>
        <p className="mt-2 text-muted-foreground">
          Welcome to your dashboard.
        </p>
        <div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
          <Card>
            <CardHeader>
              <CardTitle>Revenue</CardTitle>
            </CardHeader>
            <CardContent>
              <p className="text-3xl font-bold">$45,231.89</p>
            </CardContent>
          </Card>
          <Card>
            <CardHeader>
              <CardTitle>Subscriptions</CardTitle>
            </CardHeader>
            <CardContent>
              <p className="text-3xl font-bold">+2350</p>
            </CardContent>
          </Card>
          <Card>
            <CardHeader>
              <CardTitle>Active Users</CardTitle>
            </CardHeader>
            <CardContent>
              <p className="text-3xl font-bold">+573</p>
            </CardContent>
          </Card>
        </div>
      </main>
    </div>
  );
}

In this example:

This demonstrates the core principle: HeroUI provides the styled primitives (Card, Button), and you use the full power of Tailwind CSS to arrange them into responsive, complex layouts.

Implementing Flawless Dark Mode

One of HeroUI's most elegant features is its built-in support for dark mode. Because the init command already set up the color variables for both light (:root) and dark (.dark) themes, implementing it is surprisingly simple.

The most common approach is to use the next-themes package, which handles theme switching and persists the user's choice in local storage.

Install next-themes:Bash

npm install next-themes

Create a Theme Provider:

Create a new file, for example, at components/theme-provider.tsx.TypeScript

"use client";

import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

Wrap your Root Layout with the Provider:

In your Next.js root layout (app/layout.tsx), import and use the ThemeProvider.TypeScript

import { ThemeProvider } from "@/components/theme-provider";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={inter.className}>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

The key props here are:

Create a Theme Toggle Button:

Now, you just need a UI element to let the user switch themes.TypeScript

"use client";

import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";

export function ModeToggle() {
  const { setTheme, theme } = useTheme();

  const toggleTheme = () => {
    setTheme(theme === "light" ? "dark" : "light");
  };

  return (
    <Button variant="outline" size="icon" onClick={toggleTheme}>
      <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  );
}

This1 component uses the useTheme hook from next-themes to check the current theme and set a new one. The rotating sun and moon icons provide a nice visual transition. Simply place this <ModeToggle /> component somewhere in your UI (like a header), and you have a fully functional, persistent dark mode toggle.

Part 4: The HeroUI CLI and Its Component-Centric Workflow

The heroui-cli is more than just an installer. It is the primary tool you will use to manage and grow your component library. Its main purpose after initialization is to add new components to your project.

Adding Components: The Core Workflow

Let's say you need a dialog modal for your application. Instead of writing one from scratch, you can ask the CLI to add HeroUI's pre-built, accessible Dialog component.

The command is simple:Bash

npx heroui-cli@latest add dialog

The CLI will perform the following actions:

  1. It reads your components.json file to understand your project's structure (path aliases, TypeScript usage, etc.).
  2. It fetches the latest source code for the Dialog component and any of its dependencies (for example, Dialog might depend on Button).
  3. It places the component files directly into your components directory, for example: components/ui/dialog.tsx.
  4. It will inform you of any other dependencies you might need to install.

Now, you have a dialog.tsx file in your project. You can inspect its code, learn from it, and even modify it. If the default Dialog has a transition you don't like, you can simply open the file and change the Tailwind classes that control the animation. This level of control is the cornerstone of the HeroUI experience.

You can add multiple components at once:Bash

npx heroui-cli@latest add card button input label

This command will add all four components and their dependencies to your project in one go.

Understanding the CLI API: components.json

The components.json file is the contract between your project and the HeroUI CLI. Let's revisit its properties to understand how they influence the CLI's behavior.

By understanding this configuration, you can even manually adjust the CLI's behavior if you decide to refactor your project's structure, for example, by moving your components directory from @/components to @/ui.

Conclusion: Build Your Way with HeroUI

HeroUI represents a significant shift in how developers can think about and use UI libraries. It moves away from the one-size-fits-all, black-box model and towards a transparent, empowering, and deeply customizable developer experience. By providing unstyled, accessible components directly as source code, it strikes the perfect balance between rapid development and bespoke design.

The core strengths of HeroUI are clear:

HeroUI is for the builders, the craftspeople, and the teams who believe that their user interface is a core part of their product's identity. It doesn't give you a finished house; it gives you the highest quality materials and a perfectly organized workshop to build the house of your dreams. For your next project that demands a unique, polished, and maintainable front-end, look no further than HeroUI. It might just be the tool that empowers you to build your most heroic UI yet.

💡
Want a great API Testing tool that generates beautiful API Documentation?

Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?

Apidog delivers all your demands, and replaces Postman at a much more affordable price!
button

Explore more

AI-Powered Documentation Solutions for Modern Development

AI-Powered Documentation Solutions for Modern Development

Delve into the world of AI-powered documentation: discover top tools, key benefits, and how each tool empowers modern teams to create, manage, and publish documentation faster than ever.

4 July 2025

How to Use the dbt MCP Server

How to Use the dbt MCP Server

Discover the dbt MCP server, a tool to integrate dbt projects with AI systems. This tutorial covers installation, setup, and how it enables data discovery, querying, and automation for AI workflows.

3 July 2025

Cypher Alpha: What's the Free Mysterious OpenRouter API?

Cypher Alpha: What's the Free Mysterious OpenRouter API?

Learn to harness OpenRouter’s free Cypher Alpha AI model with Apidog for efficient API testing. This guide covers setup, examples, and benefits for developers.

2 July 2025

Practice API Design-first in Apidog

Discover an easier way to build and use APIs