组合
使用组件组合模式构建灵活的 UI
HeroUI 使用组合模式来创建灵活、可定制的组件。你可以更换渲染的元素、把组件组合在一起,并完全掌控最终的标记结构。
与框架无关的样式
HeroUI 的变体函数位于 @lenso/tokens 包中,可以独立于 React 使用。这使得 Vue、Svelte 等其他框架也能使用 HeroUI 的设计系统:
// Import directly from @lenso/tokens (framework-agnostic)import { buttonVariants } from '@lenso/tokens';
// Or import from @lenso/ui (re-exports the same functions)import { buttonVariants } from '@lenso/ui';两种导入方式的工作原理完全相同。在为非 React 框架构建,或希望避免引入 React 依赖时,请使用 @lenso/tokens。
多态样式
使用变体函数或 BEM 类,将 HeroUI 样式应用到任何元素上。可以把组件样式扩展到框架组件、原生 HTML 元素或自定义组件,并保持完整的类型安全。
示例:将 Link 设置为按钮样式
你可以使用 buttonVariants 为 Link 组件应用按钮样式:
import { buttonVariants } from '@lenso/tokens';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 '@lenso/ui';import { linkVariants } from '@lenso/tokens';import NextLink from 'next/link';
// With custom root - pass className manuallyconst 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 样式应用到特定框架的组件或自定义元素上。@lenso/tokens(与框架无关)和 @lenso/ui(重新导出)都提供了变体函数。
配合 Next.js Link 使用:
import { Link } from '@lenso/ui';import { linkVariants } from '@lenso/tokens';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 '@lenso/tokens';import Link from 'next/link';
<Link className={buttonVariants({ variant: "primary", size: "md" })} href="/dashboard"> Dashboard</Link>可用的变体函数: 每个组件都从 @lenso/tokens 导出其变体函数(buttonVariants、chipVariants、linkVariants、spinnerVariants 等)。使用它们可以在保持类型安全的同时,将 HeroUI 的设计系统应用到任何元素上。
复合组件
HeroUI 组件以复合组件的方式构建 —— 它们会导出多个协同工作的子部件。你可以通过三种灵活的方式来使用它们:
选项 1:复合模式(推荐) — 直接使用主组件,无需 .Root 后缀:
import { Alert } from '@lenso/ui';
<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 '@lenso/ui';
<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 '@lenso/ui';
<AlertRoot> <AlertIcon /> <AlertContent> <AlertTitle>Success</AlertTitle> <AlertDescription>Your changes have been saved.</AlertDescription> </AlertContent> <AlertClose /></AlertRoot>混合语法: 在同一组件中混合复合和命名导出:
import { Alert, AlertTitle, AlertDescription } from '@lenso/ui';
<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 '@lenso/ui';
// Recommended - no .Root needed<Button>Click me</Button>
// Or with .Root<Button.Root>Click me</Button.Root>
// Or named exportimport { ButtonRoot } from '@lenso/ui';<ButtonRoot>Click me</ButtonRoot>优点: 这三种模式都能提供灵活性、可定制性、可控性和一致性。选择最适合你代码库的那一种即可。
混合使用变体函数
你可以组合来自不同组件的变体函数,以创建独特的样式:
import { Link } from '@lenso/ui';import { linkVariants, buttonVariants } from '@lenso/tokens';
// Link styled with button variantsconst buttonStyles = buttonVariants({ variant: "tertiary", size: "md" });
<Link className={buttonStyles} href="https://heroui.com"> HeroUI</Link>自定义组件
通过组合 HeroUI 原语,创建你自己的组件:
import { Button, Tooltip } from '@lenso/ui';import { buttonVariants } from '@lenso/tokens';
// Link button component using variant functionsfunction LinkButton({ href, children, variant = "primary", ...props }) { return ( <a href={href} className={buttonVariants({ variant, ...props })} {...props} > {children} </a> );}
// Icon button with tooltipfunction IconButton({ icon, label, ...props }) { return ( <Tooltip> <Tooltip.Trigger> <Button isIconOnly aria-label={label} {...props}> <Icon icon={icon} /> </Button> </Tooltip.Trigger> <Tooltip.Content>{label}</Tooltip.Content> </Tooltip> );}自定义变体
通过扩展组件的变体函数来创建自定义变体:
import type { ButtonRootProps } from "@lenso/ui";import type { VariantProps } from "tailwind-variants";
import { Button } from "@lenso/ui";import { buttonVariants, tv } from "@lenso/tokens";
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 "@lenso/ui";
type MyButtonProps = ButtonRootProps;type MyAvatarProps = AvatarRootProps;替代方案 — 对象样式语法:
import { Button, Avatar } from "@lenso/ui";
type MyButtonProps = Button["RootProps"];type MyAvatarProps = Avatar["RootProps"];注意: 不再支持 Button.RootProps 这种命名空间语法。请使用 Button["RootProps"] 或命名导入。
自定义 DOM 元素
在以下组件上使用 render prop,可以渲染自定义组件来代替默认的 DOM 元素。
例如,你可以渲染一个 Motion 按钮,并利用其状态来驱动动画。
import {Button} from '@lenso/ui';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 '@lenso/ui';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 '@lenso/tokens';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 '@lenso/tokens';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 或其他框架使用:
由于 @lenso/tokens 没有 React 依赖,你可以直接在任何框架中使用它:
<script setup>import { buttonVariants } from '@lenso/tokens';
const primaryButton = buttonVariants({ variant: "primary" });</script>
<template> <button :class="primaryButton">Click me</button></template>