Skip to content
Lenso UI

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 级别的语义变体
classNamestring-附加 CSS 类
childrenReact.ReactNode-卡片内容

Card.Header

Prop类型默认值描述
classNamestring-附加 CSS 类
childrenReact.ReactNode-头部内容

Card.Title

Prop类型默认值描述
classNamestring-附加 CSS 类
childrenReact.ReactNode-标题内容(渲染为 h3)

Card.Description

Prop类型默认值描述
classNamestring-附加 CSS 类
childrenReact.ReactNode-描述内容(渲染为 p)

Card.Content

Prop类型默认值描述
classNamestring-附加 CSS 类
childrenReact.ReactNode-主要内容

Card.Footer

Prop类型默认值描述
classNamestring-附加 CSS 类
childrenReact.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>

相关组件