Card 卡片
灵活容器组件,用于分组相关内容与操作
用法
import { Card } from "@lenso/ui";此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.// oxlint-disable jsx-a11y/prefer-tag-over-role -- A named SVG needs img semantics; an HTML img cannot render this icon.import { CircleDollar } from "@gravity-ui/icons";import { Card, Link } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { s } from "./display.stylex";
export function Default() { return ( <Card xstyle={s.card400}> <CircleDollar aria-label="Dollar sign icon" role="img" {...stylex.props(s.primary, s.icon6)} /> <Card.Header> <Card.Title>Become an Acme Creator!</Card.Title> <Card.Description> Visit the Acme Creator Hub to sign up today and start earning credits from your fans and followers. </Card.Description> </Card.Header> <Card.Footer> <Link aria-label="Go to Acme Creator Hub (opens in new tab)" href="https://heroui.com" rel="noopener noreferrer" target="_blank" > Creator Hub <Link.Icon aria-hidden="true" /> </Link> </Card.Footer> </Card> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
组件结构
import { Card } from "@lenso/ui";
export default () => ( <Card> <Card.Header> <Card.Title /> <Card.Description /> </Card.Header> <Card.Content /> <Card.Footer /> </Card>);示例
变体
Card 提供语义变体,描述 prominence 级别而非具体视觉样式,允许主题以不同方式解读:
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Card } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { s } from "./display.stylex";
const cards = [ { variant: "transparent", title: "Transparent", description: "Minimal prominence with transparent background", content: "Use for less important content or nested cards", }, { variant: "default", title: "Default", description: "Standard card appearance (bg-surface)", content: "The default card variant for most use cases", }, { variant: "secondary", title: "Secondary", description: "Medium prominence (bg-surface-secondary)", content: "Use to draw moderate attention", }, { variant: "tertiary", title: "Tertiary", description: "Higher prominence (bg-surface-tertiary)", content: "Use for primary or featured content", },] as const;export function Variants() { return ( <div {...stylex.props(s.column4)}> {cards.map((card) => ( <Card key={card.variant} xstyle={s.card320} variant={card.variant}> <Card.Header> <Card.Title>{card.title}</Card.Title> <Card.Description>{card.description}</Card.Description> </Card.Header> <Card.Content> <p>{card.content}</p> </Card.Content> </Card> ))} </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
transparent- 最低 prominence,透明背景(适合嵌套卡片)default- 大多数场景的标准卡片(surface-secondary)secondary- 中等 prominence 以吸引适度注意(surface-tertiary)tertiary- 更高 prominence 用于重要内容(surface-tertiary)
水平布局
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Button, Card, CloseButton } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { s } from "./display.stylex";
export function Horizontal() { return ( <Card xstyle={s.horizontal}> <div {...stylex.props(s.imageFrame)}> <img alt="Cherries" {...stylex.props(s.imageZoom)} loading="lazy" src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/cherries.jpeg" /> </div> <div {...stylex.props(s.flexContent)}> <Card.Header xstyle={s.gap1}> <Card.Title xstyle={s.titleInset}>Become an ACME Creator!</Card.Title> <Card.Description> Lorem ipsum dolor sit amet consectetur. Sed arcu donec id aliquam dolor sed amet faucibus etiam. </Card.Description> <CloseButton aria-label="Close banner" xstyle={s.close} /> </Card.Header> <Card.Footer xstyle={s.footer}> <div {...stylex.props(s.column)}> <span {...stylex.props(s.textSm, s.medium, s.foreground)}>Only 10 spots</span> <span {...stylex.props(s.textXs, s.muted)}>Submission ends Oct 10.</span> </div> <Button xstyle={s.fullAuto}>Apply Now</Button> </Card.Footer> </div> </Card> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
带头像
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Avatar, Card } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { s } from "./display.stylex";
export function WithAvatar() { return ( <div {...stylex.props(s.wrap4)}> <Card xstyle={s.card200}> <img alt="Indie Hackers community" {...stylex.props(s.imageCommunity)} loading="lazy" src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/demo1.jpg" /> <Card.Header> <Card.Title>Indie Hackers</Card.Title> <Card.Description>148 members</Card.Description> </Card.Header> <Card.Footer xstyle={s.row2}> <Avatar aria-label="Martha's profile picture" xstyle={s.avatarTiny}> <Avatar.Image alt="Martha's avatar" src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg" /> <Avatar.Fallback xstyle={s.textXs}>IH</Avatar.Fallback> </Avatar> <span {...stylex.props(s.textXs)}>By Martha</span> </Card.Footer> </Card> <Card xstyle={s.card200}> <img alt="AI Builders community" {...stylex.props(s.imageCommunity)} loading="lazy" src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/demo2.jpg" /> <Card.Header> <Card.Title>AI Builders</Card.Title> <Card.Description>362 members</Card.Description> </Card.Header> <Card.Footer xstyle={s.row2}> <Avatar aria-label="John's profile picture" xstyle={s.avatarTiny}> <Avatar.Image alt="John's avatar - blue themed" src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg" /> <Avatar.Fallback xstyle={s.textXs}>B</Avatar.Fallback> </Avatar> <span {...stylex.props(s.textXs)}>By John</span> </Card.Footer> </Card> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
带图片
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.// oxlint-disable jsx-a11y/prefer-tag-over-role -- A named SVG needs img semantics; an HTML img cannot render this icon.import { CircleDollar } from "@gravity-ui/icons";import { Avatar, Button, Card, CloseButton, Link } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { s } from "./display.stylex";
const DOCS = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/";const AVATARS = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/";
export function WithImages() { return ( <div {...stylex.props(s.gridOuter)}> <div {...stylex.props(s.grid)}> <Card xstyle={s.firstCard}> <div {...stylex.props(s.imageFrame)}> <img alt="Cherries" {...stylex.props(s.imageZoom)} loading="lazy" src={`${DOCS}cherries.jpeg`} /> </div> <div {...stylex.props(s.flexContent)}> <Card.Header xstyle={s.gap1}> <Card.Title xstyle={s.titleInset}>Become an ACME Creator!</Card.Title> <Card.Description> Lorem ipsum dolor sit amet consectetur. Sed arcu donec id aliquam dolor sed amet faucibus etiam. </Card.Description> <CloseButton aria-label="Close banner" xstyle={s.close} /> </Card.Header> <Card.Footer xstyle={s.footer}> <div {...stylex.props(s.column)}> <span {...stylex.props(s.textSm, s.medium, s.foreground)}>Only 10 spots</span> <span {...stylex.props(s.textXs, s.muted)}>Submission ends Oct 10.</span> </div> <Button xstyle={s.fullAuto}>Apply Now</Button> </Card.Footer> </div> </Card> <div {...stylex.props(s.gridRow)}> <div {...stylex.props(s.gridHalf)}> <Card xstyle={s.span12}> <div {...stylex.props(s.close, s.z10)}> <CloseButton aria-label="Close notification" /> </div> <Card.Header xstyle={s.gap3}> <CircleDollar aria-label="Dollar sign icon" {...stylex.props(s.primary, s.icon8, s.shrink0)} role="img" /> <div {...stylex.props(s.column1)}> <span {...stylex.props(s.textXs, s.medium, s.muted, s.uppercase)}>PAYMENT</span> <Card.Title xstyle={s.responsiveTitle}>You can now withdraw on crypto</Card.Title> <Card.Description xstyle={s.responsiveXs}> Add your wallet in settings to withdraw </Card.Description> </div> </Card.Header> <Card.Footer> <Link aria-label="Go to settings" href="#" rel="noopener noreferrer"> Go to settings <Link.Icon aria-hidden="true" /> </Link> </Card.Footer> </Card> <div {...stylex.props(s.gridRow)}> <Card xstyle={s.smallCard}> <Card.Header> <Avatar xstyle={s.roundedAvatar}> <Avatar.Image alt="Demo 1" src={`${DOCS}demo1.jpg`} /> <Avatar.Fallback>JK</Avatar.Fallback> </Avatar> </Card.Header> <Card.Content xstyle={s.mt1}> <p {...stylex.props(s.textSm, s.leading4, s.medium)}>Indie Hackers</p> <p {...stylex.props(s.textXs, s.muted)}>148 members</p> </Card.Content> <Card.Footer xstyle={s.row2}> <Avatar xstyle={s.avatarMini}> <Avatar.Image alt="John" src={`${AVATARS}red.jpg`} /> <Avatar.Fallback>JK</Avatar.Fallback> </Avatar> <p {...stylex.props(s.textXs, s.muted)}>By John</p> </Card.Footer> </Card> <Card xstyle={s.smallCard}> <Card.Header> <Avatar xstyle={s.roundedAvatar}> <Avatar.Image alt="Demo 2" src={`${DOCS}demo2.jpg`} /> <Avatar.Fallback>AB</Avatar.Fallback> </Avatar> </Card.Header> <Card.Content xstyle={s.mt1}> <p {...stylex.props(s.textSm, s.leading4, s.medium)}>AI Builders</p> <p {...stylex.props(s.textXs, s.muted)}>362 members</p> </Card.Content> <Card.Footer xstyle={s.row2}> <Avatar xstyle={s.avatarMini}> <Avatar.Image alt="John" src={`${AVATARS}blue.jpg`} /> <Avatar.Fallback>M</Avatar.Fallback> </Avatar> <p {...stylex.props(s.textXs, s.muted)}>By Martha</p> </Card.Footer> </Card> </div> </div> <Card xstyle={s.robotCard}> <img alt="NEO Home Robot" aria-hidden="true" {...stylex.props(s.imageCover)} src={`${DOCS}neo2.jpeg`} /> <Card.Header xstyle={[s.z10, s.white]}> <Card.Title xstyle={[s.textXs, s.semibold, s.tracking, s.black70]}>NEO</Card.Title> <Card.Description xstyle={[s.textSm, s.medium, s.black50]}> Home Robot </Card.Description> </Card.Header> <Card.Footer xstyle={s.robotFooter}> <div> <div {...stylex.props(s.textSm, s.medium, s.black)}>Available soon</div> <div {...stylex.props(s.textXs, s.black60)}>Get notified</div> </div> <Button xstyle={s.whiteButton} size="sm" variant="tertiary"> Notify me </Button> </Card.Footer> </Card> </div> <div {...stylex.props(s.gridRow)}> <Card xstyle={s.robotLarge}> <img alt="NEO Home Robot" aria-hidden="true" {...stylex.props(s.imageCover)} src={`${DOCS}neo1.jpeg`} /> <Card.Footer xstyle={s.robotLargeFooter}> <div> <div {...stylex.props(s.responsiveBase, s.medium, s.black)}>NEO</div> <div {...stylex.props(s.responsiveXs, s.medium, s.black50)}>$499/m</div> </div> <Button xstyle={s.whiteButton} size="sm" variant="tertiary"> Get now </Button> </Card.Footer> </Card> <div {...stylex.props(s.robotStack)}> <Card xstyle={s.eventCard} variant="transparent"> <img alt="Futuristic Robot" {...stylex.props(s.eventImage)} loading="lazy" src={`${DOCS}robot1.jpeg`} /> <div {...stylex.props(s.eventContent)}> <Card.Title xstyle={s.textSm}>Bridging the Future</Card.Title> <Card.Description xstyle={s.textXs}>Today, 6:30 PM</Card.Description> </div> </Card> <Card xstyle={s.eventCard} variant="transparent"> <img alt="Avocado" {...stylex.props(s.eventImage)} loading="lazy" src={`${DOCS}avocado.jpeg`} /> <div {...stylex.props(s.eventContent)}> <Card.Title xstyle={s.textSm}>Avocado Hackathon</Card.Title> <Card.Description xstyle={s.textXs}>Wed, 4:30 PM</Card.Description> </div> </Card> <Card xstyle={s.eventCard} variant="transparent"> <img alt="Sound Electro event" {...stylex.props(s.eventImage)} loading="lazy" src={`${DOCS}oranges.jpeg`} /> <div {...stylex.props(s.eventContent)}> <Card.Title xstyle={s.textSm}>Sound Electro | Beyond art</Card.Title> <Card.Description xstyle={s.textXs}>Fri, 8:00 PM</Card.Description> </div> </Card> </div> </div> </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
搭配表单
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Button, Card, Form, Input, Label, Link, TextField } from "@lenso/ui";import type { FormEvent } from "react";import * as stylex from "@stylexjs/stylex";import { s } from "./display.stylex";
export function WithForm() { const onSubmit = (event: FormEvent<HTMLFormElement>) => { event.preventDefault(); window.alert("Form submitted successfully!"); }; return ( <Card xstyle={s.maxMd}> <Card.Header> <Card.Title>Login</Card.Title> <Card.Description>Enter your credentials to access your account</Card.Description> </Card.Header> <Form onSubmit={onSubmit}> <Card.Content> <div {...stylex.props(s.column4)}> <TextField name="email"> <Label>Email</Label> <Input name="email" type="email" placeholder="[email protected]" variant="secondary" /> </TextField> <TextField name="password"> <Label>Password</Label> <Input name="password" type="password" placeholder="••••••••" variant="secondary" /> </TextField> </div> </Card.Content> <Card.Footer xstyle={s.formFooter}> <Button xstyle={s.full} type="submit"> Sign In </Button> <Link xstyle={[s.centerText, s.textSm]} href="#"> Forgot password? </Link> </Card.Footer> </Form> </Card> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
自定义样式
Tailwind CSS
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Check, Star } from "@gravity-ui/icons";import { Button, Card } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { s } from "./display.stylex";
const PRO_FEATURES = [ "Unlimited projects and collaborators", "Priority support with 24h response", "Advanced analytics and exports",] as const;const styles = stylex.create({ card: { position: "relative", width: "100%", maxWidth: 448, overflow: "hidden", borderWidth: 1, borderStyle: "solid", borderColor: { default: "color-mix(in oklab, var(--accent) 20%, transparent)", ':is([data-theme="dark"] *)': "color-mix(in oklab, var(--accent) 30%, transparent)", }, backgroundImage: { default: "linear-gradient(to bottom right in oklab, color-mix(in oklab, var(--accent) 12%, transparent), var(--surface), var(--surface-secondary))", ':is([data-theme="dark"] *)': "linear-gradient(to bottom right in oklab, color-mix(in oklab, var(--accent) 20%, transparent), var(--surface), color-mix(in oklab, var(--accent) 8%, transparent))", }, boxShadow: { default: "0 10px 15px -3px color-mix(in oklab, var(--accent) 10%, transparent), 0 4px 6px -4px color-mix(in oklab, var(--accent) 10%, transparent)", ':is([data-theme="dark"] *)': "0 10px 15px -3px color-mix(in oklab, var(--accent) 5%, transparent), 0 4px 6px -4px color-mix(in oklab, var(--accent) 5%, transparent)", }, }, topGlow: { pointerEvents: "none", position: "absolute", top: -48, right: -48, width: 160, height: 160, borderRadius: 9999, filter: "blur(64px)", backgroundColor: { default: "color-mix(in oklab, var(--accent) 20%, transparent)", ':is([data-theme="dark"] *)': "color-mix(in oklab, var(--accent) 30%, transparent)", }, }, bottomGlow: { pointerEvents: "none", position: "absolute", bottom: -32, left: -32, width: 112, height: 112, borderRadius: 9999, filter: "blur(40px)", backgroundColor: { default: "color-mix(in oklab, var(--accent) 10%, transparent)", ':is([data-theme="dark"] *)': "color-mix(in oklab, var(--accent) 20%, transparent)", }, }, recommended: { width: "fit-content", borderRadius: 9999, paddingInline: 10, paddingBlock: 2, fontSize: 12, lineHeight: "16px", fontWeight: 500, letterSpacing: "0.025em", backgroundColor: { default: "color-mix(in oklab, var(--accent) 15%, transparent)", ':is([data-theme="dark"] *)': "color-mix(in oklab, var(--accent) 25%, transparent)", }, color: { default: "var(--accent)", ':is([data-theme="dark"] *)': "var(--accent-soft-foreground)", }, }, star: { display: "flex", width: 40, height: 40, flexShrink: 0, alignItems: "center", justifyContent: "center", borderRadius: 12, backgroundColor: { default: "color-mix(in oklab, var(--accent) 15%, transparent)", ':is([data-theme="dark"] *)': "color-mix(in oklab, var(--accent) 20%, transparent)", }, color: { default: "var(--accent)", ':is([data-theme="dark"] *)': "var(--accent-soft-foreground)", }, }, footer: { position: "relative", flexDirection: { default: "column", "@media (min-width: 640px)": "row" }, gap: 8, }, upgrade: { width: "100%", boxShadow: "0 4px 6px -1px color-mix(in oklab, var(--accent) 20%, transparent), 0 2px 4px -2px color-mix(in oklab, var(--accent) 20%, transparent)", },});export function CustomStyles() { return ( <Card xstyle={styles.card}> <div aria-hidden="true" {...stylex.props(styles.topGlow)} /> <div aria-hidden="true" {...stylex.props(styles.bottomGlow)} /> <Card.Header xstyle={[s.relative, s.gap3]}> <span {...stylex.props(styles.recommended)}>Recommended</span> <div {...stylex.props(s.startRow3)}> <div {...stylex.props(styles.star)}> <Star aria-hidden="true" {...stylex.props(s.icon5)} /> </div> <div {...stylex.props(s.column1)}> <Card.Title>Upgrade to Pro</Card.Title> <Card.Description> Unlock team workflows and insights built for growing products. </Card.Description> </div> </div> </Card.Header> <Card.Content xstyle={s.relative}> <ul {...stylex.props(s.column2)}> {PRO_FEATURES.map((feature) => ( <li key={feature} {...stylex.props(s.row2, s.textSm, s.muted)}> <Check aria-hidden="true" {...stylex.props(s.icon4, s.shrink0, s.accent)} /> {feature} </li> ))} </ul> </Card.Content> <Card.Footer xstyle={styles.footer}> <Button xstyle={styles.upgrade}>Upgrade now</Button> <Button xstyle={s.full} variant="secondary"> Compare plans </Button> </Card.Footer> </Card> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
全局 CSS
可使用 @layer components 指令自定义 Card 组件类。
了解更多。
@layer components { .card--secondary { @apply bg-gradient-to-br from-blue-50 to-purple-50; }
.card__title { @apply text-xl font-bold; }}样式参考
HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。
CSS 类
Card 使用 BEM 命名以实现可预测的样式(查看源码样式):
基础类 [!toc]
.card- 带 padding 与边框的基础容器.card__header- 头部区域容器.card__title- 基础字号与字重的标题.card__description- 弱化描述文本.card__content- 灵活内容容器.card__footer- 行布局的页脚
变体类 [!toc]
.card--transparent- 最低 prominence,透明背景(对应transparent变体).card--default- 标准外观,surface-secondary(默认).card--secondary- 中等 prominence,surface-tertiary(对应secondary变体).card--tertiary- 更高 prominence,surface-tertiary(对应tertiary变体)
API 参考
Card
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
variant | "transparent" | "default" | "secondary" | "tertiary" | "default" | 表示 prominence 级别的语义变体 |
className | string | - | 附加 CSS 类 |
children | React.ReactNode | - | 卡片内容 |
Card.Header
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
children | React.ReactNode | - | 头部内容 |
Card.Title
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
children | React.ReactNode | - | 标题内容(渲染为 h3) |
Card.Description
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
children | React.ReactNode | - | 描述内容(渲染为 p) |
Card.Content
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
children | React.ReactNode | - | 主要内容 |
Card.Footer
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 附加 CSS 类 |
children | React.ReactNode | - | 页脚内容 |
无障碍
import { Card } from '@lenso/ui';import { cardVariants } from '@lenso/tokens';
// Semantic markup<Card role="article" aria-labelledby="card-title"> <Card.Header> <Card.Title id="card-title">Article Title</Card.Title> </Card.Header></Card>
// Interactive cards<a className={cardVariants().base()} href="/details" aria-label="View product details"> <Card.Title>Product Name</Card.Title></a>