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.
Polymorphic Styling
Apply HeroUI styles to any element using variant functions or BEM classes. Extend component styles to framework components, native HTML elements, or custom components with full type safety.
Example: Styling a Link as a Button
You can use buttonVariants to style a Link component with button styles:
import { buttonVariants } from '@heroui/react';
import Link from 'next/link';
// Style a Next.js Link as a primary button
<Link
className={buttonVariants({ variant: "primary" })}
href="/about"
>
About
</Link>
// Style a native anchor as a secondary button with custom size
<a
className={buttonVariants({ variant: "secondary", size: "lg" })}
href="https://example.com"
>
External Link
</a>Using BEM classes directly:
import Link from 'next/link';
// Apply button styles directly using BEM classes
<Link className="button button--primary" href="/about">
About
</Link>Working with Compound Components
When using a custom root element instead of HeroUI's Root component, child components cannot access context slots. You can manually pass className to child components using variant functions or BEM classes:
import { Link, linkVariants } from '@heroui/react';
import NextLink from 'next/link';
// With custom root - pass className manually
const slots = linkVariants({ underline: "hover" });
<NextLink className={slots.base()} href="/about">
About Page
<Link.Icon className={slots.icon()} />
</NextLink>
<NextLink className="link link--underline-hover" href="/about">
About Page
<Link.Icon className="link__icon" />
</NextLink>This approach works because HeroUI's variant functions and BEM classes can be applied to any element, giving you complete flexibility to style framework components, native elements, or custom components with HeroUI's design system.
Direct Class Application
The simplest way to style links or other elements is to apply HeroUI's BEM classes directly. This approach is straightforward and works with any framework or vanilla HTML.
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.
Using Variant Functions
For more control and type safety, use variant functions to apply HeroUI styles to framework-specific components or custom elements.
With Next.js Link:
import { Link, linkVariants } from '@heroui/react';
import NextLink from 'next/link';
const slots = linkVariants({ underline: "hover" });
<NextLink className={slots.base()} href="/about">
About Page
<Link.Icon className={slots.icon()} />
</NextLink>With Button styles:
import { buttonVariants } from '@heroui/react';
import Link from 'next/link';
<Link
className={buttonVariants({ variant: "primary", size: "md" })}
href="/dashboard"
>
Dashboard
</Link>Available variant functions: Each component exports its variant function (buttonVariants, chipVariants, linkVariants, spinnerVariants, and more). Use them to apply HeroUI's design system to any element while maintaining type safety.
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.
Mixing Variant Functions
You can combine variant functions from different components to create unique styles:
import { Link, linkVariants, buttonVariants } from '@heroui/react';
// Link styled with button variants
const linkSlots = linkVariants({ underline: "hover" });
const buttonStyles = buttonVariants({ variant: "tertiary", size: "md" });
<Link
className={`${linkSlots.base()} ${buttonStyles}`}
href="https://heroui.com"
>
HeroUI
</Link>Custom Components
Create your own components by composing HeroUI primitives:
import { Button, Tooltip, buttonVariants } from '@heroui/react';
// Link button component using variant functions
function LinkButton({ href, children, variant = "primary", ...props }) {
return (
<a
href={href}
className={buttonVariants({ variant, ...props })}
{...props}
>
{children}
</a>
);
}
// 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:
Use variant functions for type-safe styling:
import { buttonVariants } from '@heroui/react';
import Link from 'next/link';
<Link
className={buttonVariants({ variant: "primary" })}
href="/dashboard"
>
Dashboard
</Link>Or apply BEM classes directly (simplest):
import Link from 'next/link';
<Link className="button button--primary" href="/dashboard">
Dashboard
</Link>With React Router:
Use variant functions:
import { buttonVariants } from '@heroui/react';
import { Link } from 'react-router-dom';
<Link
className={buttonVariants({ variant: "primary" })}
to="/dashboard"
>
Dashboard
</Link>Or apply BEM classes directly (simplest):
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