Composition

Build flexible UI with component composition patterns

Overview

HeroUI uses composition patterns to create flexible, customizable components. This allows you to:

  • Change the rendered element or component
  • Compose components together
  • Maintain full control over markup

The asChild Prop

The asChild prop lets you change what element a component renders. When asChild is true, HeroUI won't render its default element - instead, it clones the child element and merges props.

Basic Usage

import { Button } from '@heroui/react';
import Link from 'next/link';

// Renders as a Next.js Link
<Button asChild>
  <Link href="/about">About</Link>
</Button>

// Renders as a regular anchor
<Button asChild>
  <a href="https://example.com">External Link</a>
</Button>

Available Components

These components support asChild:

  • Button - Change button element
  • Alert and its parts - Alert (or Alert.Root), Alert.Icon, Alert.Content, Alert.Title, Alert.Description, Alert.Action, Alert.Close
  • Avatar and its parts - Avatar (or Avatar.Root), Avatar.Image, Avatar.Fallback
  • All other HeroUI components with root elements

Compound Components

HeroUI components are built as compound components - they export multiple parts that work together. You can use them in three flexible ways:

Use the main component directly without .Root suffix:

import { Alert } from '@heroui/react';

<Alert>
  <Alert.Icon />
  <Alert.Content>
    <Alert.Title>Success</Alert.Title>
    <Alert.Description>Your changes have been saved.</Alert.Description>
  </Alert.Content>
  <Alert.Close />
</Alert>

Option 2: Compound Pattern with .Root

Use the .Root suffix if you prefer explicit naming:

import { Alert } from '@heroui/react';

<Alert.Root>
  <Alert.Icon />
  <Alert.Content>
    <Alert.Title>Success</Alert.Title>
    <Alert.Description>Your changes have been saved.</Alert.Description>
  </Alert.Content>
  <Alert.Close />
</Alert.Root>

Option 3: Named Exports

Import each part separately:

import {
  AlertRoot,
  AlertIcon,
  AlertContent,
  AlertTitle,
  AlertDescription,
  AlertClose
} from '@heroui/react';

<AlertRoot>
  <AlertIcon />
  <AlertContent>
    <AlertTitle>Success</AlertTitle>
    <AlertDescription>Your changes have been saved.</AlertDescription>
  </AlertContent>
  <AlertClose />
</AlertRoot>

Mixed Syntax

You can mix compound and named exports in the same component:

import { Alert, AlertTitle, AlertDescription } from '@heroui/react';

<Alert>
  <Alert.Icon />
  <Alert.Content>
    <AlertTitle>Success</AlertTitle>
    <AlertDescription>Your changes have been saved.</AlertDescription>
  </Alert.Content>
  <Alert.Close />
</Alert>

Simple Components

Simple components like Button work the same way - no .Root needed:

import { Button } from '@heroui/react';

// Recommended - no .Root needed
<Button>Click me</Button>

// Or with .Root
<Button.Root>Click me</Button.Root>

// Or named export
import { ButtonRoot } from '@heroui/react';
<ButtonRoot>Click me</ButtonRoot>

Benefits of Compound Components

All three patterns provide:

  • Flexibility - Arrange parts as needed
  • Customization - Style each part independently
  • Control - Add or remove parts
  • Consistency - Choose the pattern that fits your codebase

Style Variants

HeroUI exports variant functions that can be applied to any component. This allows you to use HeroUI's design system with any element or component:

Using buttonVariants

Apply button styles to any component:

import { Link, LinkIcon, buttonVariants } from '@heroui/react';

// Link styled as a tertiary button
<Link
  className={buttonVariants({
    size: "md",
    variant: "tertiary",
    className: "px-3"
  })}
  href="https://heroui.com"
  target="_blank"
>
  HeroUI
  <LinkIcon className="h-2 w-2" />
</Link>

// Native anchor styled as primary button
<a
  className={buttonVariants({ variant: "primary" })}
  href="/dashboard"
>
  Go to Dashboard
</a>

Available Variant Functions

Each component exports its variant function:

  • buttonVariants - Button styles
  • chipVariants - Chip styles
  • linkVariants - Link styles
  • spinnerVariants - Spinner styles
  • More variant functions for each component

Custom Components

Create your own components by composing HeroUI primitives:

import { Button, Tooltip } from '@heroui/react';

// Link button component
function LinkButton({ href, children, ...props }) {
  return (
    <Button asChild {...props}>
      <a href={href}>{children}</a>
    </Button>
  );
}

// Icon button with tooltip
function IconButton({ icon, label, ...props }) {
  return (
    <Tooltip>
      <Tooltip.Trigger>
        <Button isIconOnly {...props}>
          <Icon icon={icon} />
        </Button>
      </Tooltip.Trigger>
      <Tooltip.Content>{label}</Tooltip.Content>
    </Tooltip>
  );
}

Custom Variants

You can create your own custom variants by extending the component's variant function.

import type { ButtonRootProps } from "@heroui/react";
import type { VariantProps } from "tailwind-variants";

import { Button, buttonVariants } from "@heroui/react";
import { tv } from "tailwind-variants";

const myButtonVariants = tv({
  extend: buttonVariants,
  base: "text-md text-shadow-lg font-semibold shadow-md data-[pending=true]:opacity-40",
  variants: {
    radius: {
      lg: "rounded-lg",
      md: "rounded-md",
      sm: "rounded-sm",
      full: "rounded-full",
    },
    size: {
      sm: "h-10 px-4",
      md: "h-11 px-6",
      lg: "h-12 px-8",
      xl: "h-13 px-10",
    },
    variant: {
      primary: "text-white dark:bg-white/10 dark:text-white dark:hover:bg-white/15",
    },
  },
  defaultVariants: {
    radius: "full",
    variant: "primary",
  },
});

type MyButtonVariants = VariantProps<typeof myButtonVariants>;
export type MyButtonProps = Omit<ButtonRootProps, "className"> &
  MyButtonVariants & { className?: string };

function CustomButton({ className, radius, variant, ...props }: MyButtonProps) {
  return <Button className={myButtonVariants({ className, radius, variant })} {...props} />;
}

export function CustomVariants() {
  return <CustomButton>Custom Button</CustomButton>;
}

Type References

When working with component types, use named type imports or object-style syntax:

Recommended - Named type imports:

import type { ButtonRootProps, AvatarRootProps } from "@heroui/react";

type MyButtonProps = ButtonRootProps;
type MyAvatarProps = AvatarRootProps;

Alternative - Object-style syntax:

import { Button, Avatar } from "@heroui/react";

type MyButtonProps = Button["RootProps"];
type MyAvatarProps = Avatar["RootProps"];

Note: The namespace syntax Button.RootProps is no longer supported. Use Button["RootProps"] or named imports instead.

With Next.js

Use asChild to integrate with Next.js:

import Link from 'next/link';
import { Button } from '@heroui/react';

<Button asChild variant="primary">
  <Link href="/dashboard">Dashboard</Link>
</Button>

With React Router

Use asChild to integrate with routing libraries:

import { Link } from 'react-router-dom';
import { Button } from '@heroui/react';

<Button asChild variant="primary">
  <Link to="/dashboard">Dashboard</Link>
</Button>

Next Steps