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 compositionNew 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
| v2 | v3 | What to do |
|---|---|---|
| Framer Motion animations | Native CSS transitions | Check animation timing, may need adjustment |
backdrop="blur" / "opaque" | Unified backdrop | Use default or apply custom styling |
asChild prop | Removed | Use compound components instead |
Listbox | ListBox | Rename import |
Progress | ProgressBar | Rename import |
CircularProgress | ProgressCircle | Rename import |
Divider | Separator | Rename import |
Installation
# v2npm i @heroui/react
# v3npm i @heroui/styles @heroui/reactThe 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
- HeroUI v3 Official Documentation - Full release notes and migration guide
- React Aria Components - Accessibility primitives HeroUI builds on
- Tailwind CSS v4 - Modern theming with CSS variables and OKLCH
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