Claude Code Plugins

Community-maintained marketplace

Feedback
9
0

Add or customize HeroUI components in the web application. Use when implementing new UI features with HeroUI design system or updating component styling.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name heroui-components
description Add or customize HeroUI components in the web application. Use when implementing new UI features with HeroUI design system or updating component styling.
allowed-tools Read, Edit, Write, mcp__heroui-react__list_components, mcp__heroui-react__get_component_info, mcp__heroui-react__get_component_examples

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

  1. Missing Provider: Ensure HeroUIProvider wraps your app
  2. Styling Issues: Check Tailwind CSS configuration includes HeroUI plugin
  3. Type Errors: Import types from @heroui/react
  4. SSR Issues: Mark client components with "use client"

References

  • HeroUI Documentation: Use MCP tools for component info
  • Related files:
    • apps/web/src/components/ - React components
    • apps/web/src/app/ - Pages using HeroUI
    • apps/web/tailwind.config.ts - Tailwind configuration
    • apps/web/CLAUDE.md - Web app documentation

Best Practices

  1. Use MCP Tools: Query component info before implementing
  2. Consistent Variants: Use same variant across similar components
  3. Accessibility: Always add aria labels for icon-only buttons
  4. Responsive: Test components on mobile, tablet, desktop
  5. Provider: Ensure HeroUIProvider at root level
  6. TypeScript: Leverage TypeScript types for props
  7. Testing: Wrap tests in HeroUIProvider
  8. Performance: Use client components only when needed
  9. Size Utility: Use size-* instead of h-* w-* for equal dimensions (Tailwind v3.4+)

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>