Skip to content

What's New in HeroUI v3? Complete Guide to the Major Rewrite

I spent hours fighting with HeroUI v2’s DatePicker component. It didn’t exist. Every time I needed date selection, I had to pull in a third-party library, wrestle with inconsistent styling, and pray it didn’t clash with HeroUI’s theming.

Then HeroUI v3 dropped with 20+ new components including DatePicker, Calendar, and ColorPicker. I was skeptical—another major version bump usually means more breaking changes than benefits. But after migrating a medium-sized codebase, I found v3’s compound component architecture genuinely solves real problems.

Here’s what I learned.

The Problem with v2: Black Box Components

In HeroUI v2, components were self-contained black boxes. Want to customize the Card’s header spacing? Good luck—you’d end up writing ugly override CSS or wrapping everything in custom containers.

// v2: The Card component owned its structure
<Card>
<CardHeader>I can't easily restyle this</CardHeader>
<CardBody>Content here</CardBody>
<CardFooter>Actions</CardFooter>
</Card>

The issue: CardHeader, CardBody, and CardFooter weren’t real components. They were just styled divs generated internally. You couldn’t:

  • Apply custom classes to internal elements
  • Reorder the header/footer
  • Remove parts you didn’t need
  • Add custom elements between sections

For simple use cases, this was fine. But as applications grow, the “convention over configuration” approach becomes a constraint.

v3’s Solution: Compound Component Architecture

HeroUI v3 exposes every internal piece as a real component. You control the structure.

// v3: You own the composition
<Card>
<Card.Header>
<Card.Title>Product</Card.Title>
<Card.Description>Details about this product</Card.Description>
</Card.Header>
<Card.Body>
<p>Card content goes here.</p>
</Card.Body>
<Card.Footer>
<Button variant="primary">Buy now</Button>
</Card.Footer>
</Card>

Now Card.Header, Card.Body, and Card.Footer are actual components you can style, move, or remove.

What This Enables

1. Custom layouts between sections:

<Card>
<Card.Header>Header</Card.Header>
<Divider /> {/* Add a divider between header and body */}
<Card.Body>Content</Card.Body>
</Card>

2. Conditional rendering:

<Card>
<Card.Header>{showHeader && <Card.Title>Title</Card.Title>}</Card.Header>
<Card.Body>Content</Card.Body>
{canAct && <Card.Footer>Action buttons</Card.Footer>}
</Card>

3. Custom styling per section:

<Card>
<Card.Header className="bg-gradient-to-r from-purple-500 to-pink-500">
<Card.Title>Featured</Card.Title>
</Card.Header>
<Card.Body>Content</Card.Body>
</Card>

The Trade-off: More Verbose, But Clearer

Yes, v3 requires more code. But that code is explicit about what it renders. No more wondering “what does Card actually output?”—you can see it in your JSX.

v2 approach: v3 approach:
┌─────────────────┐ ┌─────────────────┐
│ <Card /> │ │ <Card> │
│ (magic inside) │ │ <Header/> │
│ │ │ <Body/> │
│ What's inside? │ │ <Footer/> │
│ Check docs. │ │ </Card> │
└─────────────────┘ └─────────────────┘
Black box Transparent composition

New Components: The Ones I Actually Use

HeroUI v3 adds 20+ components. Here are the ones that solved my immediate problems.

DatePicker and Calendar

Finally, native date handling:

import { DatePicker, DatePickerContent, DatePickerInput } from "@heroui/react";
<DatePicker>
<DatePickerInput placeholder="Select a date" />
<DatePickerContent>
{/* Use default calendar or customize */}
</DatePickerContent>
</DatePicker>

The compound approach shines here. Want a date range picker? Combine DatePicker with RangeCalendar:

<DateRangePicker>
<DateRangePickerInput startPlaceholder="Start" endPlaceholder="End" />
<DateRangePickerContent>
<RangeCalendar />
</DateRangePickerContent>
</DateRangePicker>

ColorPicker

Another v2 gap filled. The ColorPicker includes color area, sliders, and swatches—all composable:

<ColorPicker defaultValue="#3b82f6">
<ColorArea />
<ColorSlider channel="hue" />
<ColorSlider channel="alpha" />
</ColorPicker>

Toast Notifications

No more importing react-toastify separately:

import { Toast, ToastProvider, useToast } from "@heroui/react";
function MyComponent() {
const { toast } = useToast();
const handleSave = async () => {
await saveData();
toast.success("Changes saved!");
};
}

AlertDialog

For destructive actions that need confirmation:

<AlertDialog>
<AlertDialogTrigger>
<Button variant="danger">Delete Account</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialog.Title>Are you sure?</AlertDialog.Title>
<AlertDialog.Description>
This action cannot be undone. Your data will be permanently deleted.
</AlertDialog.Description>
<AlertDialog.Actions>
<Button variant="secondary">Cancel</Button>
<Button variant="danger">Delete</Button>
</AlertDialog.Actions>
</AlertDialogContent>
</AlertDialog>

Table: Unopinionated Data Display

The Reddit thread mentions HeroUI’s Table offers “the perfect amount of functionality while not being opinionated about filtering/sorting.” This is key.

Other UI libraries force their filtering/sorting implementation on you. HeroUI’s Table gives you the structure:

<Table>
<TableHeader>
<TableColumn>Name</TableColumn>
<TableColumn>Status</TableColumn>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Project Alpha</TableCell>
<TableCell><Badge color="success">Active</Badge></TableCell>
</TableRow>
</TableBody>
</Table>

But YOU decide how to sort and filter. Pass sorted/filtered data to TableBody. No hidden state management.

Performance: Native CSS Transitions

v3 replaces Framer Motion with native CSS transitions. This matters for bundle size and runtime performance.

v2 bundle impact:

  • Framer Motion: ~25KB minified + gzipped
  • JavaScript-driven animations = CPU overhead

v3 approach:

  • Native CSS transitions
  • Zero additional JS bundle
  • GPU-accelerated by default

On low-end devices, this is noticeable. Animations stay smooth at 60fps instead of dropping frames when React is busy.

Tailwind CSS v4 Integration

HeroUI v3 embraces Tailwind v4’s CSS variables approach. Theming uses OKLCH color space:

:root {
--background: oklch(0.9702 0 0);
--foreground: oklch(0.2103 0.0059 285.89);
--accent: oklch(0.6204 0.195 253.83);
--surface: oklch(100% 0 0);
--danger: oklch(0.6532 0.2328 25.74);
--radius: 0.5rem;
}

Setup is minimal:

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

The @heroui/styles package is separate from @heroui/react. This enables headless mode—you can use HeroUI’s logic without its styles, then apply your own.

Migration: What Broke

I’ll be honest: the migration wasn’t painless. Here’s what changed.

Breaking Changes Table

v2v3What to do
Framer Motion animationsNative CSS transitionsCheck animation timing, may need adjustment
backdrop="blur" / "opaque"Unified backdropUse default or apply custom styling
asChild propRemovedUse compound components instead
ListboxListBoxRename import
ProgressProgressBarRename import
CircularProgressProgressCircleRename import
DividerSeparatorRename import

Installation

Terminal window
# v2
npm i @heroui/react
# v3
npm i @heroui/styles @heroui/react

The styles package is now separate for headless mode support.

React Aria Components: The Underlying Change

HeroUI migrated from React Aria hooks to React Aria Components. This matters for accessibility.

Before (hooks):

import { useButton } from 'react-aria';
function Button(props) {
const ref = useRef();
const { buttonProps } = useButton(props, ref);
return <button {...buttonProps} ref={ref}>{props.children}</button>;
}

After (components):

import { Button as AriaButton } from 'react-aria-components';
function Button(props) {
return <AriaButton {...props} />;
}

React Aria Components provide:

  • Declarative API consistent with modern React
  • Better keyboard navigation out of the box
  • ARIA attributes handled automatically
  • Consistent cross-browser behavior

AI Tooling Integration

This surprised me. HeroUI v3 ships with first-class AI developer experience:

  • MCP Server: AI assistants can query component documentation directly
  • Agent Skills: Pre-built skills for Cursor and Claude Code
  • LLMs.txt files: Machine-readable API summaries

Why this matters: when I asked Claude Code to “add a DatePicker to the form,” it knew exactly what to import and how to compose DatePickerInput with DatePickerContent. No documentation hunting.

When to Upgrade

Upgrade if:

  • You need DatePicker, ColorPicker, Toast, or AlertDialog
  • Your team wants fine-grained component control
  • You’re targeting Tailwind CSS v4
  • Performance on low-end devices is a concern

Wait if:

  • Your v2 codebase is stable and you don’t need new components
  • You rely heavily on Framer Motion for complex animations
  • You’re mid-sprint with no time for migration testing

Summary

HeroUI v3 isn’t just a version bump—it’s a paradigm shift from “magic components” to “composable primitives.” The compound architecture trades brevity for control, which I’ve found worthwhile as applications grow in complexity.

The new DatePicker alone justified the migration for me. Add in native CSS transitions, Tailwind v4 integration, and proper accessibility through React Aria Components, and v3 becomes a solid foundation for new projects.

For existing projects, evaluate whether you need the new components or the flexibility. If v2 is working, there’s no rush. But if you’ve been fighting component limitations, v3’s transparency might be exactly what you need.


References

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments