Composition
Build flexible UI with component composition patterns
HeroUI uses composition patterns to create flexible, customizable components. Change the rendered element, compose components together, and maintain full control over markup.
The asChild Prop
The asChild prop lets you change what element a component renders. When asChild is true, HeroUI clones the child element and merges props instead of rendering its default element.
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: Button, Alert and its parts, Avatar and its parts, and all other HeroUI components with root elements support asChild.
Direct Class Application
The simplest way to style links or other elements is to apply HeroUI's BEM classes directly. This is often simpler than using asChild or variant functions.
With Next.js Link:
import Link from 'next/link';
<Link className="button button--tertiary" href="/">
Return Home
</Link>With native anchor:
<a className="button button--primary" href="/dashboard">
Go to Dashboard
</a>Available button classes:
.button— Base button styles.button--primary,.button--secondary,.button--tertiary,.button--danger,.button--ghost— Variants.button--sm,.button--md,.button--lg— Sizes.button--icon-only— Icon-only button
This approach works because HeroUI uses BEM classes that can be applied to any element. It's perfect when you don't need the component's interactive features (like onPress handlers) and just want the visual styling.
Compound Components
HeroUI components are built as compound components—they export multiple parts that work together. 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: 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: All three patterns provide flexibility, customization, control, and consistency. Choose the pattern that fits your codebase.
Style Variants
HeroUI exports variant functions that can be applied to any component. Use HeroUI's design system with any element or component.
Using buttonVariants:
import { Link, LinkIcon, buttonVariants } from '@heroui/react';
// Link styled as a tertiary button
<Link.Root
className={buttonVariants({
size: "md",
variant: "tertiary",
className: "px-3"
})}
href="https://heroui.com"
target="_blank"
>
HeroUI
<LinkIcon className="h-2 w-2" />
</Link.Root>
// 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, chipVariants, linkVariants, spinnerVariants, and more).
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
Create 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.
Framework Integration
With Next.js:
You can use asChild:
import Link from 'next/link';
import { Button } from '@heroui/react';
<Button asChild variant="primary">
<Link href="/dashboard">Dashboard</Link>
</Button>Or apply classes directly (simpler):
import Link from 'next/link';
<Link className="button button--primary" href="/dashboard">
Dashboard
</Link>With React Router:
You can use asChild:
import { Link } from 'react-router-dom';
import { Button } from '@heroui/react';
<Button asChild variant="primary">
<Link to="/dashboard">Dashboard</Link>
</Button>Or apply classes directly (simpler):
import { Link } from 'react-router-dom';
<Link className="button button--primary" to="/dashboard">
Dashboard
</Link>Next Steps
- Learn about Styling components
- Explore Animation options
- Browse Components for more examples