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 parts - Root, Icon, Content, Title, Description, Action, Close
  • Avatar - Change root element
  • More components coming soon

Compound Components

HeroUI components are built as compound components - they export multiple parts that work together:

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>

This pattern provides:

  • Flexibility - Arrange parts as needed
  • Customization - Style each part independently
  • Control - Add or remove parts

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, 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
  <Link.Icon 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 content={label}>
      <Button isIconOnly {...props}>
        <Icon icon={icon} />
      </Button>
    </Tooltip>
  );
}

Custom Variants

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

import type {ButtonProps} 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<ButtonProps, "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>;
}

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