ProComponents, templates & AI tooling
HeroUI
27.7k

组合

使用组件组合模式构建灵活的 UI

HeroUI 使用组合模式来创建灵活、可定制的组件。你可以更换渲染的元素、把组件组合在一起,并完全掌控最终的标记结构。

与框架无关的样式

HeroUI 的变体函数位于 @heroui/styles 包中,可以独立于 React 使用。这使得 Vue、Svelte 等其他框架也能使用 HeroUI 的设计系统:

// 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';

两种导入方式的工作原理完全相同。在为非 React 框架构建,或希望避免引入 React 依赖时,请使用 @heroui/styles

多态样式

使用变体函数或 BEM 类,将 HeroUI 样式应用到任何元素上。可以把组件样式扩展到框架组件、原生 HTML 元素或自定义组件,并保持完整的类型安全。

示例:将 Link 设置为按钮样式

你可以使用 buttonVariants 为 Link 组件应用按钮样式:

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>

直接使用 BEM 类:

import Link from 'next/link';

// Apply button styles directly using BEM classes
<Link className="button button--primary" href="/about">
  About
</Link>

配合复合组件使用

当使用自定义根元素而非 HeroUI 的 Root 组件时,子组件无法访问 context slot。你可以使用变体函数或 BEM 类,手动将 className 传递给子组件:

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>

这种方法之所以可行,是因为 HeroUI 的变体函数和 BEM 类可以应用到任何元素上,让你能够灵活地用 HeroUI 的设计系统为框架组件、原生元素或自定义组件设置样式。

直接应用类名

为链接或其他元素设置样式最简单的方法,就是直接使用 HeroUI 的 BEM 类。这种方法简单直接,适用于任何框架或纯 HTML。

配合 Next.js Link 使用:

import Link from 'next/link';

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

配合原生 anchor 使用:

<a className="button button--primary" href="/dashboard">
  Go to Dashboard
</a>

可用的按钮类名:

  • .button — 基础按钮样式
  • .button--primary.button--secondary.button--tertiary.button--danger.button--ghost — 变体
  • .button--sm.button--md.button--lg — 尺寸
  • .button--icon-only — 仅图标按钮

这种方法之所以可行,是因为 HeroUI 使用了 BEM 类,可以应用到任何元素上。当你不需要组件的交互功能(例如 onPress 事件处理器)、只想要视觉样式时,这种方式非常合适。

使用变体函数

如需更多控制和类型安全,可以使用变体函数将 HeroUI 样式应用到特定框架的组件或自定义元素上。@heroui/styles(与框架无关)和 @heroui/react(重新导出)都提供了变体函数。

配合 Next.js Link 使用:

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>

配合 Button 样式:

import { buttonVariants } from '@heroui/styles';
import Link from 'next/link';

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

可用的变体函数: 每个组件都从 @heroui/styles 导出其变体函数(buttonVariantschipVariantslinkVariantsspinnerVariants 等)。使用它们可以在保持类型安全的同时,将 HeroUI 的设计系统应用到任何元素上。

复合组件

HeroUI 组件以复合组件的方式构建 —— 它们会导出多个协同工作的子部件。你可以通过三种灵活的方式来使用它们:

选项 1:复合模式(推荐) — 直接使用主组件,无需 .Root 后缀:

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>

选项 2:使用 .Root 的复合模式 — 如果你喜欢显式命名,可以添加 .Root 后缀:

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>

选项 3:命名导出 — 单独导入每个部分:

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>

混合语法: 在同一组件中混合复合和命名导出:

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>

简单组件:Button 这样的简单组件以同样的方式工作 —— 无需 .Root

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>

优点: 这三种模式都能提供灵活性、可定制性、可控性和一致性。选择最适合你代码库的那一种即可。

混合使用变体函数

你可以组合来自不同组件的变体函数,以创建独特的样式:

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>

自定义组件

通过组合 HeroUI 原语,创建你自己的组件:

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>
  );
}

自定义变体

通过扩展组件的变体函数来创建自定义变体:

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>;
}

类型引用: 在使用组件类型时,可以使用命名类型导入或对象样式语法。

推荐 — 命名类型导入:

import type { ButtonRootProps, AvatarRootProps } from "@heroui/react";

type MyButtonProps = ButtonRootProps;
type MyAvatarProps = AvatarRootProps;

替代方案 — 对象样式语法:

import { Button, Avatar } from "@heroui/react";

type MyButtonProps = Button["RootProps"];
type MyAvatarProps = Avatar["RootProps"];

注意: 不再支持 Button.RootProps 这种命名空间语法。请使用 Button["RootProps"] 或命名导入。

自定义 DOM 元素

在以下组件上使用 render prop,可以渲染自定义组件来代替默认的 DOM 元素。

例如,你可以渲染一个 Motion 按钮,并利用其状态来驱动动画。

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>

render prop 对于从客户端路由库渲染链接组件,或复用已有的展示型组件也很有用。

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>

请遵循以下规则,以免破坏组件的行为和无障碍能力:

  • 始终渲染期望的元素类型(例如,如果期望的是 <button>,就不要渲染 <a>)。如果检测到不匹配,开发期间你会看到警告。
  • 只渲染单个根 DOM 元素(不要使用 fragment)。
  • 始终将传入的 props 传递给底层 DOM 元素,并根据需要通过 mergeProps 与你自己的 props 合并。

框架集成

配合 Next.js 使用:

使用变体函数获得类型安全的样式:

import { buttonVariants } from '@heroui/styles';
import Link from 'next/link';

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

或者直接应用 BEM 类(最简单):

import Link from 'next/link';

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

配合 React Router 使用:

使用变体函数:

import { buttonVariants } from '@heroui/styles';
import { Link } from 'react-router-dom';

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

或者直接应用 BEM 类(最简单):

import { Link } from 'react-router-dom';

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

配合 Vue、Svelte 或其他框架使用:

由于 @heroui/styles 没有 React 依赖,你可以直接在任何框架中使用它:

<script setup>
import { buttonVariants } from '@heroui/styles';

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

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

下一步

本页目录