Claude-skill-registry heroui-components
Add or customize HeroUI components in the web application. Use when implementing new UI features with HeroUI design system or updating component styling.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/heroui-components" ~/.claude/skills/majiayu000-claude-skill-registry-heroui-components && rm -rf "$T"
manifest:
skills/data/heroui-components/SKILL.mdsource content
HeroUI Components Skill
This skill helps you work with HeroUI (Hero UI / NextUI v3) components in
apps/web/.
When to Use This Skill
- Adding new HeroUI components to pages
- Customizing HeroUI component variants
- Implementing forms with HeroUI inputs
- Creating modal dialogs and drawers
- Building navigation with HeroUI components
- Debugging HeroUI styling issues
HeroUI Overview
HeroUI is a modern React UI library built on Tailwind CSS and React Aria, providing accessible components.
Discovery and Research
List Available Components
// Use MCP tool to discover components mcp__heroui-react__list_components()
Get Component Information
// Get details about a specific component mcp__heroui-react__get_component_info({ component: "Button" }) mcp__heroui-react__get_component_info({ component: "Input" })
Get Usage Examples
// Get examples for a component mcp__heroui-react__get_component_examples({ component: "Modal" })
Installation and Setup
HeroUI is already installed in the web app. Import from
@heroui/react:
import { Button, Input, Card } from "@heroui/react";
Common Components
1. Button Component
import { Button } from "@heroui/react"; export function ActionButton() { return ( <> {/* Basic button */} <Button>Click me</Button> {/* Color variants */} <Button color="primary">Primary</Button> <Button color="secondary">Secondary</Button> <Button color="success">Success</Button> <Button color="warning">Warning</Button> <Button color="danger">Danger</Button> {/* Size variants */} <Button size="sm">Small</Button> <Button size="md">Medium</Button> <Button size="lg">Large</Button> {/* Variants */} <Button variant="solid">Solid</Button> <Button variant="bordered">Bordered</Button> <Button variant="light">Light</Button> <Button variant="flat">Flat</Button> <Button variant="faded">Faded</Button> <Button variant="shadow">Shadow</Button> <Button variant="ghost">Ghost</Button> {/* With icon */} <Button startContent={<SearchIcon />}>Search</Button> <Button endContent={<ArrowIcon />}>Next</Button> {/* Loading state */} <Button isLoading>Loading...</Button> {/* Disabled */} <Button isDisabled>Disabled</Button> </> ); }
2. Input Component
import { Input } from "@heroui/react"; export function SearchForm() { return ( <> {/* Basic input */} <Input type="text" label="Search" placeholder="Enter search term" /> {/* With validation */} <Input type="email" label="Email" placeholder="you@example.com" isRequired errorMessage="Please enter a valid email" /> {/* With start/end content */} <Input type="text" label="Price" placeholder="0.00" startContent={<span>$</span>} endContent={<span>SGD</span>} /> {/* Variants */} <Input variant="flat" /> <Input variant="bordered" /> <Input variant="faded" /> <Input variant="underlined" /> {/* Colors */} <Input color="primary" /> <Input color="success" /> <Input color="danger" /> </> ); }
3. Card Component
import { Card, CardHeader, CardBody, CardFooter, Button } from "@heroui/react"; export function CarCard({ car }: { car: Car }) { return ( <Card className="max-w-md"> <CardHeader className="flex gap-3"> <div className="flex flex-col"> <p className="text-md font-bold">{car.make}</p> <p className="text-small text-default-500">{car.model}</p> </div> </CardHeader> <CardBody> <p>Year: {car.year}</p> <p>Price: ${car.price?.toLocaleString()}</p> </CardBody> <CardFooter> <Button color="primary" variant="flat"> View Details </Button> </CardFooter> </Card> ); }
4. Modal Component
"use client"; import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, useDisclosure, } from "@heroui/react"; export function CarDetailsModal({ car }: { car: Car }) { const { isOpen, onOpen, onOpenChange } = useDisclosure(); return ( <> <Button onPress={onOpen}>View Details</Button> <Modal isOpen={isOpen} onOpenChange={onOpenChange}> <ModalContent> {(onClose) => ( <> <ModalHeader className="flex flex-col gap-1"> {car.make} {car.model} </ModalHeader> <ModalBody> <p>Year: {car.year}</p> <p>Registration: {car.registrationDate}</p> <p>Fuel Type: {car.fuelType}</p> </ModalBody> <ModalFooter> <Button color="danger" variant="light" onPress={onClose}> Close </Button> <Button color="primary" onPress={onClose}> Save </Button> </ModalFooter> </> )} </ModalContent> </Modal> </> ); }
5. Table Component
import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, } from "@heroui/react"; export function CarTable({ cars }: { cars: Car[] }) { return ( <Table aria-label="Car registrations table"> <TableHeader> <TableColumn>MAKE</TableColumn> <TableColumn>MODEL</TableColumn> <TableColumn>YEAR</TableColumn> <TableColumn>PRICE</TableColumn> </TableHeader> <TableBody> {cars.map((car) => ( <TableRow key={car.id}> <TableCell>{car.make}</TableCell> <TableCell>{car.model}</TableCell> <TableCell>{car.year}</TableCell> <TableCell>${car.price?.toLocaleString()}</TableCell> </TableRow> ))} </TableBody> </Table> ); }
6. Tabs Component
import { Tabs, Tab } from "@heroui/react"; export function DataTabs() { return ( <Tabs aria-label="Data options"> <Tab key="cars" title="Car Registrations"> <CarRegistrationData /> </Tab> <Tab key="coe" title="COE Results"> <COEBiddingData /> </Tab> <Tab key="pqp" title="PQP Data"> <PQPData /> </Tab> </Tabs> ); }
7. Select Component
import { Select, SelectItem } from "@heroui/react"; export function CarMakeSelector() { const makes = ["Toyota", "Honda", "BMW", "Mercedes"]; return ( <Select label="Select car make" placeholder="Choose a make" className="max-w-xs" > {makes.map((make) => ( <SelectItem key={make} value={make}> {make} </SelectItem> ))} </Select> ); }
8. Dropdown Component
import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button, } from "@heroui/react"; export function ActionsDropdown() { return ( <Dropdown> <DropdownTrigger> <Button variant="bordered">Actions</Button> </DropdownTrigger> <DropdownMenu aria-label="Actions"> <DropdownItem key="view">View</DropdownItem> <DropdownItem key="edit">Edit</DropdownItem> <DropdownItem key="delete" className="text-danger" color="danger"> Delete </DropdownItem> </DropdownMenu> </Dropdown> ); }
Form Handling
Complete Form Example
"use client"; import { useState } from "react"; import { Input, Button, Textarea } from "@heroui/react"; import { createBlogPost } from "@/actions/blog"; export function CreatePostForm() { const [isLoading, setIsLoading] = useState(false); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); setIsLoading(true); const formData = new FormData(e.currentTarget); const result = await createBlogPost(formData); setIsLoading(false); if (result.success) { // Handle success } } return ( <form onSubmit={handleSubmit} className="space-y-4"> <Input name="title" label="Post Title" placeholder="Enter post title" isRequired /> <Textarea name="content" label="Content" placeholder="Enter post content" minRows={5} isRequired /> <Input name="tags" label="Tags" placeholder="Enter tags separated by commas" /> <Button type="submit" color="primary" isLoading={isLoading} className="w-full" > Create Post </Button> </form> ); }
Theming and Customization
Theme Provider Setup
// app/providers.tsx "use client"; import { HeroUIProvider } from "@heroui/react"; export function Providers({ children }: { children: React.ReactNode }) { return ( <HeroUIProvider> {children} </HeroUIProvider> ); } // app/layout.tsx import { Providers } from "./providers"; export default function RootLayout({ children }) { return ( <html lang="en"> <body> <Providers>{children}</Providers> </body> </html> ); }
Custom Colors via Tailwind
// tailwind.config.ts import { heroui } from "@heroui/react"; export default { plugins: [ heroui({ themes: { light: { colors: { primary: { DEFAULT: "#0070F3", foreground: "#FFFFFF", }, focus: "#0070F3", }, }, }, }), ], };
Component-Level Customization
import { Button } from "@heroui/react"; export function CustomButton() { return ( <Button className="bg-gradient-to-r from-blue-500 to-purple-500" variant="shadow" > Custom Styled Button </Button> ); }
Accessibility
HeroUI components are built on React Aria for accessibility:
import { Button } from "@heroui/react"; export function AccessibleButton() { return ( <Button aria-label="Close dialog" aria-describedby="close-description" > Close </Button> ); }
Responsive Design
import { Button } from "@heroui/react"; export function ResponsiveButton() { return ( <Button size={{ initial: "sm", // Mobile sm: "md", // Tablet lg: "lg", // Desktop }} className="w-full sm:w-auto" > Responsive Button </Button> ); }
Common Patterns
Loading States
"use client"; import { useState } from "react"; import { Button } from "@heroui/react"; export function SubmitButton() { const [isLoading, setIsLoading] = useState(false); async function handleClick() { setIsLoading(true); await performAction(); setIsLoading(false); } return ( <Button onPress={handleClick} isLoading={isLoading} color="primary" > {isLoading ? "Processing..." : "Submit"} </Button> ); }
Controlled Inputs
"use client"; import { useState } from "react"; import { Input } from "@heroui/react"; export function ControlledInput() { const [value, setValue] = useState(""); return ( <Input value={value} onValueChange={setValue} label="Search" placeholder="Type to search..." /> ); }
Testing HeroUI Components
// __tests__/components/car-card.test.tsx import { render, screen } from "@testing-library/react"; import { HeroUIProvider } from "@heroui/react"; import CarCard from "../car-card"; function renderWithProvider(component: React.ReactElement) { return render( <HeroUIProvider>{component}</HeroUIProvider> ); } describe("CarCard", () => { it("renders car information", () => { const car = { make: "Toyota", model: "Camry", year: 2024 }; renderWithProvider(<CarCard car={car} />); expect(screen.getByText("Toyota")).toBeInTheDocument(); expect(screen.getByText("Camry")).toBeInTheDocument(); }); });
Debugging Tips
- Missing Provider: Ensure HeroUIProvider wraps your app
- Styling Issues: Check Tailwind CSS configuration includes HeroUI plugin
- Type Errors: Import types from
@heroui/react - SSR Issues: Mark client components with
"use client"
References
- HeroUI Documentation: Use MCP tools for component info
- Related files:
- React componentsapps/web/src/components/
- Pages using HeroUIapps/web/src/app/
- Tailwind configurationapps/web/tailwind.config.ts
- Web app documentationapps/web/CLAUDE.md
Best Practices
- Use MCP Tools: Query component info before implementing
- Consistent Variants: Use same variant across similar components
- Accessibility: Always add aria labels for icon-only buttons
- Responsive: Test components on mobile, tablet, desktop
- Provider: Ensure HeroUIProvider at root level
- TypeScript: Leverage TypeScript types for props
- Testing: Wrap tests in HeroUIProvider
- Performance: Use client components only when needed
- Size Utility: Use
instead ofsize-*
for equal dimensions (Tailwind v3.4+)h-* w-*
Size Utility Convention
When styling HeroUI components with equal height and width, use the
size-* utility:
// ✅ Good - Use size-* for equal dimensions <Button isIconOnly className="size-10"> <Icon className="size-4" /> </Button> // ❌ Avoid - Redundant h-* and w-* <Button isIconOnly className="h-10 w-10"> <Icon className="h-4 w-4" /> </Button>