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 elementAlertand its parts -Alert(orAlert.Root),Alert.Icon,Alert.Content,Alert.Title,Alert.Description,Alert.Action,Alert.CloseAvatarand its parts -Avatar(orAvatar.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:
Option 1: Compound Pattern (Recommended)
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 styleschipVariants- Chip styleslinkVariants- Link stylesspinnerVariants- 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
- Learn about Styling components
- Explore Animation options
- Browse Components for more examples