---
title: Composition
description: Build flexible UI with component composition patterns
icon: layers
---

HeroUI uses composition patterns to create flexible, customizable components. Change the rendered element, compose components together, and maintain full control over markup.

## Framework-Agnostic Styles

HeroUI's variant functions are available in the `@heroui/styles` package, which can be used independently of React. This enables Vue, Svelte, and other frameworks to use HeroUI's design system:

```tsx
// Import directly from @heroui/styles (framework-agnostic)
import { buttonVariants } from '@heroui/styles';

// Or import from @heroui/react (re-exports the same functions)
import { buttonVariants } from '@heroui/react';
```

Both imports work identically. Use `@heroui/styles` when building for non-React frameworks or when you want to avoid pulling in React dependencies.

## 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:

```tsx
import { buttonVariants } from '@heroui/styles';
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:**

```tsx
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:

```tsx
import { Link } from '@heroui/react';
import { linkVariants } from '@heroui/styles';
import NextLink from 'next/link';

// With custom root - pass className manually
const slots = linkVariants();

<NextLink className={slots.base()} href="/about">
  About Page
  <Link.Icon className={slots.icon()} />
</NextLink>

<NextLink className="link" 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](https://getbem.com/) classes directly. This approach is straightforward and works with any framework or vanilla HTML.

**With Next.js Link:**
```tsx
import Link from 'next/link';

<Link className="button button--tertiary" href="/">
  Return Home
</Link>
```

**With native anchor:**
```tsx
<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](https://getbem.com/) 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. Variant functions are available from both `@heroui/styles` (framework-agnostic) and `@heroui/react` (re-exports).

**With Next.js Link:**

```tsx
import { Link } from '@heroui/react';
import { linkVariants } from '@heroui/styles';
import NextLink from 'next/link';

const slots = linkVariants();

<NextLink className={slots.base()} href="/about">
  About Page
  <Link.Icon className={slots.icon()} />
</NextLink>
```

**With Button styles:**

```tsx
import { buttonVariants } from '@heroui/styles';
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) from `@heroui/styles`. 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:

```tsx
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:

```tsx
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:

```tsx
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:

```tsx
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:

```tsx
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:

```tsx
import { Link } from '@heroui/react';
import { linkVariants, buttonVariants } from '@heroui/styles';

// Link styled with button variants
const buttonStyles = buttonVariants({ variant: "tertiary", size: "md" });

<Link
  className={buttonStyles}
  href="https://heroui.com"
>
  HeroUI
</Link>
```

## Custom Components

Create your own components by composing HeroUI primitives:

```tsx
import { Button, Tooltip } from '@heroui/react';
import { buttonVariants } from '@heroui/styles';

// 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:

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

import { Button } from "@heroui/react";
import { buttonVariants, tv } from "@heroui/styles";

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:**
```tsx
import type { ButtonRootProps, AvatarRootProps } from "@heroui/react";

type MyButtonProps = ButtonRootProps;
type MyAvatarProps = AvatarRootProps;
```

**Alternative — Object-style syntax:**
```tsx
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.

## Custom DOM Element

Use the `render` prop on the following components to render a custom component in place of the default DOM element.

For example, you can render a [Motion](https://motion.dev/) button and use the state to drive an animation.

```tsx
import {Button} from '@heroui/react';
import {motion} from 'motion/react';

<Button
  render={(domProps, {isPressed}) => (
    <motion.button
      {...domProps}
      animate={{scale: isPressed ? 0.9 : 1}} />
  )}>
  Press me
</Button>
```

The `render` prop is also useful for rendering link components from client-side routers, or reusing existing presentational components.

```tsx
import {Link} from '@heroui/react';
import NextLink from 'next/link';

<Link
  render={({ref, ...domProps}) => (
    <NextLink {...domProps} ref={ref as React.Ref<HTMLAnchorElement>} href="/privacy-policy" />
  )}
>
  Privacy Policy
</Link>
```

Follow these rules to avoid breaking the behavior and accessibility of the component:
- Always render the expected element type (e.g. if `<button>` is expected, do not render an `<a>`). You will see a warning in development if a mismatch is detected.
- Only render a single root DOM element (no fragments).
- Always pass the provided props the underlying DOM element, merging with your own props via `mergeProps` as needed.


## Framework Integration

**With Next.js:**

Use variant functions for type-safe styling:
```tsx
import { buttonVariants } from '@heroui/styles';
import Link from 'next/link';

<Link
  className={buttonVariants({ variant: "primary" })}
  href="/dashboard"
>
  Dashboard
</Link>
```

Or apply BEM classes directly (simplest):
```tsx
import Link from 'next/link';

<Link className="button button--primary" href="/dashboard">
  Dashboard
</Link>
```

**With React Router:**

Use variant functions:
```tsx
import { buttonVariants } from '@heroui/styles';
import { Link } from 'react-router-dom';

<Link
  className={buttonVariants({ variant: "primary" })}
  to="/dashboard"
>
  Dashboard
</Link>
```

Or apply BEM classes directly (simplest):
```tsx
import { Link } from 'react-router-dom';

<Link className="button button--primary" to="/dashboard">
  Dashboard
</Link>
```

**With Vue, Svelte, or Other Frameworks:**

Since `@heroui/styles` has no React dependencies, you can use it directly in any framework:
```vue
<script setup>
import { buttonVariants } from '@heroui/styles';

const primaryButton = buttonVariants({ variant: "primary" });
</script>

<template>
  <button :class="primaryButton">Click me</button>
</template>
```


## Next Steps

- Learn about [Styling](/docs/handbook/styling) components
- Explore [Animation](/docs/handbook/animation) options
- Browse [Components](/docs/react/components) for more examples
