A modern design studio with UI components on screen and a red Maine Coon cat resting on the desk.

HeroUI: Beautiful by Default, Customizable by Design

The Orange Cat
The Orange Cat

If you have ever tried to build an in-house component library, you already know the trap. You want components that are accessible (proper keyboard navigation, focus management, screen-reader semantics), themeable (your brand, your colors, dark mode), and genuinely nice to look at out of the box. Getting one of those right is hard. Getting all three is a full-time project that quietly eats quarters.

HeroUI is the library that hands you all three. It combines the accessibility rigor of React Aria with the utility-first styling of Tailwind CSS, shipping 75+ polished, themeable, accessible components so your team can build product instead of re-inventing a design system. It targets SaaS apps, dashboards and admin panels, e-commerce, and marketing sites — anywhere you need a consistent, professional UI quickly.

You may know HeroUI by its old name. The project started life in 2021 as NextUI, and was rebranded to HeroUI in 2024 (the npm scope moved from @nextui-org/* to @heroui/*). Every docs page still carries the "Previously NextUI" parenthetical for recognition. This article focuses on the current v3 release, which is a ground-up rewrite — and a notably simpler one to set up than what older tutorials describe.

Why Teams Reach For It

  • Accessible by default. React Aria Components handle the genuinely hard parts — focus, keyboard interaction, ARIA wiring — so you do not have to become an ARIA expert.
  • Tailwind-native styling. Components style the same way your team already works. There is no separate CSS-in-JS runtime to learn or ship.
  • Polished defaults plus deep theming. Every design token is a CSS custom property in the OKLCH color space, so re-branding is plain CSS, and theme switching happens via data attributes with no JavaScript.
  • Compound components. Every internal piece is a real element you can style, move, swap, or remove — no fighting prop-drilled classNames objects.
  • An installed, upgradeable design system. Unlike copy-paste approaches, you get a versioned package and free upgrades.
  • AI-native tooling. HeroUI ships an MCP server, an llms.txt file, and installable agent skills for editors like Cursor and Claude Code.

A quick note on the v2 versus v3 split, because it matters for accuracy. v2 (the classic NextUI model) required a Tailwind plugin in tailwind.config.js, a root <HeroUIProvider>, and Framer Motion for animation. v3 drops all of that. The examples below are v3 unless stated otherwise.

Getting It Into Your Project

HeroUI v3 requires React 19 and Tailwind CSS v4. Installation is two packages.

npm install @heroui/styles @heroui/react
yarn add @heroui/styles @heroui/react

The setup that follows is the single most important detail in this whole article. In your global stylesheet (often globals.css), add two imports — and the order matters. Tailwind must come first:

@import "tailwindcss";
@import "@heroui/styles";

That is the entire setup. There is no tailwind.config.js plugin and no provider component to wrap your app in. If your components ever render unstyled, the import order is the first thing to check.

Your First Components

With styles imported, components are plain imports — no provider, no context, no ceremony:

import { Button } from "@heroui/react";

export default function App() {
  return <Button>My Button</Button>;
}

One thing that surprises developers coming from native DOM props: HeroUI follows React Aria's API conventions. Instead of disabled and onClick, you use isDisabled and onPress. The same pattern shows up across the library with props like isSelected:

import { Button } from "@heroui/react";

export default function SaveButton() {
  return (
    <Button isDisabled onPress={() => console.log("pressed")}>
      Save
    </Button>
  );
}

The onPress handler is not just a renamed click. React Aria normalizes pointer, touch, and keyboard activation into a single, consistent event — which is part of how HeroUI gets its accessibility right without you thinking about it.

Composing With Compound Components

The headline feature of v3 is its compound component API. Rather than passing a tangle of slot props, you compose UI from real child elements you can style and rearrange. A Card is built from Card.Header and Card.Content:

import { Card } from "@heroui/react";

function ProfileCard() {
  return (
    <Card>
      <Card.Header>Jane Doe</Card.Header>
      <Card.Content>Frontend engineer</Card.Content>
    </Card>
  );
}

Because each piece is a genuine element, you can apply Tailwind classes directly to the part you care about, drop in extra markup between sections, or omit pieces entirely. The same idea extends to interactive components — a Select, for instance, exposes Select.Item children rather than asking you to pass an array of option objects. This makes the markup read like the UI it produces, which is a quiet but real win for maintainability.

Theming Without JavaScript

HeroUI's theming is built entirely on CSS custom properties. Every color, radius, and spacing token is a variable defined in the OKLCH color space, which gives perceptually uniform, predictable color manipulation. To re-brand, you override the relevant variables in your own CSS — no plugin configuration, no provider props.

Dark mode (and any number of custom themes) is driven by data attributes on a parent element. Switching themes is as simple as toggling that attribute:

function ThemeToggle() {
  const toggle = () => {
    const root = document.documentElement;
    const next = root.dataset.theme === "dark" ? "light" : "dark";
    root.dataset.theme = next;
  };

  return <button onClick={toggle}>Toggle theme</button>;
}

Because the switch is a pure attribute change, there is no flash of re-rendered components and no JavaScript animation runtime involved. HeroUI v3 uses native CSS transitions and keyframes rather than Framer Motion, which keeps the bundle smaller and the animations GPU-accelerated. The class names follow a BEM-style convention, so if you ever need a global override that the variables do not cover, you can target a component's internal element from plain CSS.

Keeping the Bundle Honest

The convenience of importing everything from @heroui/react comes with a cost: the full barrel pulls in React Aria and React Stately, which are not small. For bundle-sensitive applications, HeroUI publishes individual, tree-shakeable per-component packages so you only ship what you use:

npm install @heroui/button @heroui/modal
import { Button } from "@heroui/button";
import { Modal } from "@heroui/modal";

There is also a standalone @heroui/styles package that contains only the theme and CSS, which you already installed above. The combination of granular packages and no CSS-in-JS runtime means a careful integration can stay lean even as you adopt more of the library.

Building Alongside AI Assistants

One genuinely modern touch: HeroUI treats AI coding assistants as first-class consumers. It hosts an llms.txt file at heroui.com/llms.txt that describes the library in an LLM-friendly format, ships an MCP server (@heroui/react-mcp) so an agent can query component APIs directly, and offers installable agent skills via its CLI:

npx heroui-cli agents-md

This generates editor-specific instruction files for tools like Cursor, Claude Code, Windsurf, and Copilot, so your assistant suggests correct HeroUI patterns — onPress instead of onClick, compound children instead of slot props — instead of guessing from stale training data. If you are building with an AI pair, it is a meaningful reduction in friction.

A Few Things Worth Knowing

A handful of gotchas will save you debugging time. The Tailwind setup is mandatory and version-sensitive — v3 needs Tailwind v4 and that two-line import in the correct order. The React Aria prop names (onPress, isDisabled, isSelected) trip up anyone expecting native DOM attributes. And if you are migrating from v2 or pre-rename NextUI, treat it as a real project, not a version bump: the provider is gone, the Tailwind plugin became a CSS import, Framer Motion was dropped, and many components became compound (Card.Header rather than CardHeader). There is a full migration guide in the v3 docs, but budget the effort. HeroUI is open source under a permissive license.

Wrapping Up

HeroUI sits in a sweet spot that few libraries reach. It is more opinionated and batteries-included than headless primitives like Radix or React Aria on their own, yet far less rigid than a Material-Design-bound system like MUI — and it leans on Tailwind, so it never asks you to learn a bespoke styling layer. Compared to copy-paste approaches, you trade a little ownership for consistency and upgrades you get for free.

If your stack already includes Tailwind and React 19, the v3 setup is about as frictionless as a full design system gets: install two packages, add two CSS imports, and start composing accessible components that look good immediately. For the long tail of brand customization, the CSS-variable theming means your designers can push the look anywhere without touching a single component's internals. Beautiful by default, customizable by design — the tagline earns its keep.