skills/heroui/SKILL.md
--- name: heroui description: Build accessible UIs using HeroUI v3 components (React + Tailwind CSS v4 + React Aria). Use when creating React interfaces, selecting UI components, implementing forms, navigation, overlays, or data display. Use when installing HeroUI v3, customizing themes, accessing component documentation, building with compound components, or working with component APIs. Keywords: HeroUI, Hero UI, heroui, React Aria, accessible components, Tailwind CSS v4, @heroui/react@beta, @h
npx skillsauth add ruchernchong/claude-kit skills/herouiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
HeroUI v3 is a component library built on Tailwind CSS v4 and React Aria Components, providing accessible, customizable UI components for React applications.
This guide is for HeroUI v3 ONLY. Do NOT use any prior knowledge of HeroUI v2.
| Feature | v2 (DO NOT USE) | v3 (USE THIS) |
|---------|-----------------|---------------|
| Provider | <HeroUIProvider> required | No Provider needed |
| Animations | framer-motion package | CSS-based, no extra deps |
| Component API | Flat props: <Card title="x"> | Compound: <Card><Card.Header> |
| Event handlers | onClick | onPress (React Aria) |
| Styling | Tailwind v3 + @heroui/theme | Tailwind v4 + @heroui/styles@beta |
| Packages | @heroui/system, @heroui/theme | @heroui/react@beta, @heroui/styles@beta |
// DO NOT DO THIS - v2 pattern
import { HeroUIProvider } from '@heroui/react';
import { motion } from 'framer-motion';
<HeroUIProvider>
<Card title="Product" description="A great product" />
</HeroUIProvider>
// DO THIS - v3 pattern (no provider, compound components)
import { Card } from '@heroui/react@beta';
<Card>
<Card.Header>
<Card.Title>Product</Card.Title>
<Card.Description>A great product</Card.Description>
</Card.Header>
</Card>
Always fetch v3 docs before implementing. Do not assume v2 patterns work.
primary, secondary, tertiary) over visual descriptionsoklch color spaceFetch component documentation via MDX routes:
https://v3.heroui.com/docs/react/components/{component-name}.mdx
Examples:
https://v3.heroui.com/docs/react/components/button.mdxhttps://v3.heroui.com/docs/react/components/modal.mdxhttps://v3.heroui.com/docs/react/components/form.mdxhttps://v3.heroui.com/docs/react/components/tabs.mdxGetting Started Guides:
https://v3.heroui.com/docs/react/getting-started/design-principles.mdxhttps://v3.heroui.com/docs/react/getting-started/styling.mdxhttps://v3.heroui.com/docs/react/getting-started/theming.mdxhttps://v3.heroui.com/docs/react/getting-started/colors.mdxLLMs.txt Documentation:
https://v3.heroui.com/react/llms.txthttps://v3.heroui.com/react/llms-full.txtExecute these scripts for reliable, up-to-date component information directly from the HeroUI API:
node scripts/list_components.mjs
Returns all available HeroUI v3 components with version info in JSON format.
node scripts/get_component_docs.mjs Button
node scripts/get_component_docs.mjs Button Card TextField
Returns complete MDX documentation including imports, usage, variants, props, and examples.
node scripts/get_source.mjs Accordion
node scripts/get_source.mjs Button Card
Returns the React/TypeScript implementation source with GitHub links. Useful for understanding component internals.
node scripts/get_styles.mjs Button
node scripts/get_styles.mjs Button Card Chip
Returns BEM CSS classes for styling with GitHub links. Shows all variants and states.
node scripts/get_theme.mjs
Returns theme CSS variables and design tokens (oklch format) organized by common/light/dark modes.
node scripts/get_docs.mjs /docs/react/getting-started/theming
node scripts/get_docs.mjs /docs/react/releases/v3-0-0-beta-3
Returns non-component MDX documentation (guides, releases, principles).
Note: For component docs, use get_component_docs.mjs instead.
CRITICAL: HeroUI v3 is currently in BETA. Always use @beta tag when installing packages.
npm i @heroui/styles@beta @heroui/react@beta tailwind-variants
Or with other package managers:
pnpm add @heroui/styles@beta @heroui/react@beta tailwind-variantsyarn add @heroui/styles@beta @heroui/react@beta tailwind-variantsbun add @heroui/styles@beta @heroui/react@beta tailwind-variantsnpm i @heroui/styles@beta @heroui/react@beta tailwind-variants tailwindcss @tailwindcss/postcss postcss
app/globals.css:/* Tailwind CSS v4 - Must be first */
@import "tailwindcss";
/* HeroUI v3 styles - Must be after Tailwind */
@import "@heroui/styles";
app/layout.tsx:import "./globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
{/* No Provider needed in HeroUI v3! */}
{children}
</body>
</html>
);
}
postcss.config.mjs):export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
Important Notes:
"use client" directive for components with event handlers (onPress, onClick)Link component with className="link" for HeroUI styled linksCard.Header, Card.Content)npm i @heroui/styles@beta @heroui/react@beta tailwind-variants tailwindcss @tailwindcss/postcss postcss
styles/globals.css:/* Tailwind CSS v4 - Must be first */
@import "tailwindcss";
/* HeroUI v3 styles - Must be after Tailwind */
@import "@heroui/styles";
pages/_app.tsx:import type { AppProps } from "next/app";
import "../styles/globals.css";
export default function MyApp({ Component, pageProps }: AppProps) {
return (
// No Provider needed in HeroUI v3!
<Component {...pageProps} />
);
}
postcss.config.mjs):export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
npm i @heroui/styles@beta @heroui/react@beta tailwind-variants tailwindcss @tailwindcss/vite @vitejs/plugin-react
src/index.css:/* Tailwind CSS v4 - Must be first */
@import "tailwindcss";
/* HeroUI v3 styles - Must be after Tailwind */
@import "@heroui/styles";
src/main.tsx:import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
{/* No Provider needed in HeroUI v3! */}
<App />
</React.StrictMode>
);
vite.config.ts):import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});
npm i @heroui/styles@beta @heroui/react@beta tailwind-variants tailwindcss @tailwindcss/vite @astrojs/react
src/styles/global.css:/* Tailwind CSS v4 - Must be first */
@import "tailwindcss";
/* HeroUI v3 styles - Must be after Tailwind */
@import "@heroui/styles";
astro.config.mjs):import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
import react from "@astrojs/react";
export default defineConfig({
integrations: [react()],
vite: {
plugins: [tailwindcss()],
},
});
src/layouts/Layout.astro:---
import "../styles/global.css";
---
Important Notes:
client:load, client:visible, etc.)npm i @heroui/styles@beta @heroui/react@beta tailwind-variants tailwindcss @tailwindcss/postcss postcss
/* Tailwind CSS v4 - Must be first */
@import "tailwindcss";
/* HeroUI v3 styles - Must be after Tailwind */
@import "@heroui/styles";
import "./styles/globals.css";
import { createRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
const root = createRoot(container!);
root.render(
// No Provider needed in HeroUI v3!
<App />
);
postcss.config.js):module.exports = {
plugins: {
"@tailwindcss/postcss": {},
},
};
Card.Header, Card.Content patternonPress event handlersBefore using any component, verify it exists in HeroUI v3:
Check component documentation:
https://v3.heroui.com/docs/react/components/{component-name}.mdxhttps://v3.heroui.com/docs/react/components/button.mdxView component list:
https://v3.heroui.com/react/llms.txt for available componentsComponent naming:
Button, Card, TextField)HeroUI v3 uses compound component patterns. Each component has subcomponents:
Example - Card Component:
<Card> {/* Root component */}
<Card.Header> {/* Subcomponent */}
<Card.Title> {/* Subcomponent */}
<Card.Description> {/* Subcomponent */}
</Card.Header>
<Card.Content> {/* Subcomponent */}
<Card.Footer> {/* Subcomponent */}
</Card>
Key Points:
Card.Header)Component Documentation:
https://v3.heroui.com/docs/react/components/{name}.mdxComponent Examples:
Component Props:
primary, secondary, danger) not raw colorsComponent Source Code:
https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/react/src/components/{component}/{component}.tsxhttps://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/styles/components/{component}.cssprimary, secondary, tertiary, danger, ghost, outline)HeroUI uses semantic naming to communicate functional intent and hierarchy:
| Variant | Functional Purpose | Usage |
|---------|-------------------|-------|
| primary | Main action to move forward | 1 per context |
| secondary | Alternative actions | Multiple allowed |
| tertiary | Dismissive actions (cancel, skip) | Sparingly |
| danger | Destructive actions | When needed |
| ghost | Low-emphasis actions | When visual weight should be minimal |
| outline | Secondary actions | When bordered style is needed |
<Button variant="primary">Save</Button>
<Button variant="secondary">Edit</Button>
<Button variant="tertiary">Cancel</Button>
<Button variant="danger">Delete</Button>
import { Button } from '@heroui/react@beta';
function MyComponent() {
return (
<Button variant="primary" onPress={() => console.log('Pressed!')}>
Click me
</Button>
);
}
<Button>
{({ isPressed, isHovered }) => (
<span className={isPressed ? 'scale-95' : ''}>
{isHovered ? 'Go!' : 'Click'}
</span>
)}
</Button>
.button[data-hovered="true"] { background: var(--accent-hover); }
.button[data-pressed="true"] { transform: scale(0.97); }
.button[data-focus-visible="true"] { outline: 2px solid var(--focus); }
.button[data-disabled="true"] { opacity: var(--disabled-opacity); }
@layer components {
.button {
@apply font-semibold uppercase;
}
.button--primary {
@apply bg-gradient-to-r from-purple-500 to-pink-500;
}
}
import { Form, TextField, Input, Label, FieldError, Button } from '@heroui/react@beta';
<Form onSubmit={handleSubmit}>
<TextField name="email" type="email" isRequired>
<Label>Email</Label>
<Input />
<FieldError />
</TextField>
<Button type="submit" variant="primary">Submit</Button>
</Form>
For detailed patterns, see component-patterns.md.
HeroUI uses CSS variables with oklch color space:
:root {
--accent: oklch(0.6204 0.195 253.83);
--accent-foreground: var(--snow);
--background: oklch(0.9702 0 0);
--foreground: var(--eclipse);
--success: oklch(0.7329 0.1935 150.81);
--warning: oklch(0.7819 0.1585 72.33);
--danger: oklch(0.6532 0.2328 25.74);
}
Color naming:
--accent)-foreground = text color (e.g., --accent-foreground)Theme switching:
<html class="dark" data-theme="dark">
HeroUI v3 uses CSS custom properties organized by category:
Theme Variables Source:
https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/styles/themes/default/variables.cssTheme Categories:
--accent, --success, --danger, --background, --foregroundCustomizing Theme Variables:
:root {
/* Override accent color */
--accent: oklch(0.7 0.25 260);
--accent-foreground: var(--snow);
/* Override border radius */
--radius: 0.75rem;
/* Custom spacing */
--spacing-4: 1rem;
}
Dark Mode Variables:
[data-theme="dark"],
.dark {
--background: oklch(0.1 0 0);
--foreground: oklch(0.95 0 0);
--accent: oklch(0.65 0.2 260);
}
Theme Variable Naming:
--accent)-foreground = text color (e.g., --accent-foreground)oklch() color space for better color manipulationFor detailed theming, see theming-customization.md.
Fetch component documentation from v3.heroui.com:
Pattern:
https://v3.heroui.com/docs/react/components/{component-name}.mdx
Examples:
https://v3.heroui.com/docs/react/components/button.mdxhttps://v3.heroui.com/docs/react/components/modal.mdxhttps://v3.heroui.com/docs/react/components/form.mdxhttps://v3.heroui.com/docs/react/components/tabs.mdxWhat's Included:
Pattern:
https://v3.heroui.com/docs/react/getting-started/{topic}.mdx
Available Guides:
https://v3.heroui.com/docs/react/getting-started/design-principles.mdxhttps://v3.heroui.com/docs/react/getting-started/styling.mdxhttps://v3.heroui.com/docs/react/getting-started/theming.mdxhttps://v3.heroui.com/docs/react/getting-started/colors.mdxhttps://v3.heroui.com/docs/react/getting-started/quick-start.mdxFor programmatic access:
https://v3.heroui.com/react/llms.txthttps://v3.heroui.com/react/llms-full.txtUsage:
Access component implementation source code:
Pattern:
https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/react/src/components/{component}/{component}.tsx
Examples:
https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/react/src/components/button/button.tsxhttps://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/react/src/components/card/card.tsxUse Cases:
Note: Do NOT copy source code directly - use components via @heroui/react@beta imports.
Access component CSS styles and BEM classes:
Pattern:
https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/styles/components/{component}.css
Examples:
https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/styles/components/button.csshttps://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/styles/components/modal.cssUse Cases:
Important: These are framework-agnostic styles from @heroui/styles package. Choose one approach:
@heroui/react for full React components with accessibility@heroui/styles for CSS-only styling without JavaScriptTheme Variables:
https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/packages/styles/themes/default/variables.css
Use Cases:
Built on React Aria for WCAG 2.1 AA compliance:
Key Practices:
aria-label for interactive elementsFor comprehensive guidance, see accessibility-guide.md.
HeroUI components are built on React Aria for accessibility, but ensure:
<button>, <a>, <label>, <table>) before ARIAaria-label<label> or aria-labelalt (or alt="" if decorative)aria-hidden="true"aria-live="polite"<h1>–<h6>focus-visible:ring-* or equivalent:focus-visible over :focus (avoid focus ring on click):focus-within for compound controlsoutline-none / outline: none without focus replacementExample:
.button:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
autocomplete and meaningful nametype (email, tel, url, number) and inputmodehtmlFor or wrapping control)spellCheck={false})… and show example patternonPaste + preventDefault)autocomplete="off" on non-auth fields (triggers password managers)truncate, line-clamp-*, or break-wordsmin-w-0 to allow text truncationExample:
<div className="min-w-0">
<p className="truncate">{longText}</p>
</div>
{items.length === 0 && (
<EmptyState message="No items found" />
)}
<img> needs explicit width and height (prevents CLS)loading="lazy"priority or fetchpriority="high"virtua, content-visibility: auto)<link rel="preconnect"> for CDN/asset domains<link rel="preload" as="font"> with font-display: swapgetBoundingClientRect, offsetHeight, scrollTop)<a>/<Link> (Cmd/Ctrl+click, middle-click support)nuqs or similar)beforeunload or router guard)touch-action: manipulation (prevents double-tap zoom delay)-webkit-tap-highlight-color set intentionallyoverscroll-behavior: contain in modals/drawers/sheetsinert on dragged elementsautoFocus sparingly—desktop only, single primary input; avoid on mobileenv(safe-area-inset-*) for notchesoverflow-x-hidden on containersIntl.DateTimeFormat not hardcoded formatsIntl.NumberFormat not hardcoded formatsAccept-Language / navigator.languages, not IPTechnical anti-patterns to avoid:
user-scalable=no or maximum-scale=1 disabling zoomtransition: all (list properties explicitly)outline-none without focus-visible replacementonClick navigation without <a><div> or <span> with click handlers (should be <button>).map() without virtualizationaria-labelIntl.*)Use semantic variants to communicate functional intent:
primary - Main action (1 per context)secondary - Alternative actions (multiple allowed)tertiary - Dismissive actions (cancel, skip)danger - Destructive actionsghost - Low-emphasis actions (when visual weight should be minimal)outline - Secondary actions (when bordered style is needed)Don't use raw colors - semantic variants adapt to themes and accessibility.
HeroUI's compound components enable flexible layouts:
// Card component structure
<Card>
<Card.Header>
<Card.Title>Title</Card.Title>
<Card.Description>Description</Card.Description>
</Card.Header>
<Card.Content>
{/* Main content */}
</Card.Content>
<Card.Footer>
<Button variant="primary">Action</Button>
</Card.Footer>
</Card>
When customizing HeroUI components:
className for component-specific styling:root for light mode[data-theme="dark"] or .dark for dark modeoklch() color space for better color manipulationtools
Update a GitHub issue with new title, body, labels, or assignees
development
Audit and fix Tailwind CSS anti-patterns. Enforces spacing direction (bottom-only), size-* usage, gap preference, 8px grid, and other best practices.
documentation
Update and maintain CLAUDE.md and README.md documentation
development
Run security audit with GitLeaks pre-commit hook setup and code analysis