HextaUI

Card

Una superficie para agrupar contenido, con tres variantes, contenido multimedia de borde a borde, radios concéntricos y enlaces en toda la tarjeta.

Team plan
Billed monthly. Renews on October 28, 2026.

12 of 20 seats used. Invite teammates or remove inactive members to free up seats.

import { IconDotsVertical } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function CardDemo() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Team plan</CardTitle>
        <CardDescription>
          Billed monthly. Renews on October 28, 2026.
        </CardDescription>
        <CardAction>
          <Button variant="ghost" size="icon-sm" aria-label="More options">
            <IconDotsVertical />
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <p>
          12 of 20 seats used. Invite teammates or remove inactive members to
          free up seats.
        </p>
      </CardContent>
      <CardFooter>
        <Button>Manage seats</Button>
        <Button variant="outline">View invoices</Button>
      </CardFooter>
    </Card>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/card.json

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardLink,
  CardTitle,
} from "@/components/ui/card"
<Card>
  <CardHeader>
    <CardTitle>Team plan</CardTitle>
    <CardDescription>Billed monthly.</CardDescription>
    <CardAction>
      <Button variant="ghost" size="icon-sm" aria-label="More options">
        <IconDotsVertical />
      </Button>
    </CardAction>
  </CardHeader>
  <CardContent>12 of 20 seats used.</CardContent>
  <CardFooter>
    <Button>Manage seats</Button>
  </CardFooter>
</Card>
Card
├── CardHeader
│   ├── CardTitle
│   │   └── CardLink
│   ├── CardDescription
│   └── CardAction
├── CardContent
└── CardFooter

Variantes

default se apoya en un anillo muy fino con una sombra suave, outline quita el relleno y la sombra, y muted usa una superficie rellena para secciones más discretas.

import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function CardVariants() {
  return (
    <div className="grid w-full gap-4 sm:grid-cols-3">
      <Card>
        <CardHeader>
          <CardTitle>Default</CardTitle>
          <CardDescription>Hairline ring and a soft shadow.</CardDescription>
        </CardHeader>
      </Card>
      <Card variant="outline">
        <CardHeader>
          <CardTitle>Outline</CardTitle>
          <CardDescription>Ring only, no fill, no shadow.</CardDescription>
        </CardHeader>
      </Card>
      <Card variant="muted">
        <CardHeader>
          <CardTitle>Muted</CardTitle>
          <CardDescription>Filled surface for quiet sections.</CardDescription>
        </CardHeader>
      </Card>
    </div>
  )
}

Tamaños

size="sm" reduce juntos el espaciado, el radio y el tamaño del título, de modo que una tarjeta compacta siga pareciendo de la misma familia. size="flush" elimina el espaciado interior para diseños personalizados como listas y filas plegables que aportan su propio relleno.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function CardSizes() {
  return (
    <div className="grid w-full gap-4 sm:grid-cols-2">
      <Card>
        <CardHeader>
          <CardTitle>Default</CardTitle>
          <CardDescription>Spacing and radius scale together.</CardDescription>
        </CardHeader>
        <CardContent>
          <p>Deploys run on every push to main.</p>
        </CardContent>
        <CardFooter>
          <Button variant="outline">Configure</Button>
        </CardFooter>
      </Card>
      <Card size="sm">
        <CardHeader>
          <CardTitle>Small</CardTitle>
          <CardDescription>Spacing and radius scale together.</CardDescription>
        </CardHeader>
        <CardContent>
          <p>Deploys run on every push to main.</p>
        </CardContent>
        <CardFooter>
          <Button size="sm" variant="outline">
            Configure
          </Button>
        </CardFooter>
      </Card>
    </div>
  )
}

Media

Una imagen, vídeo, picture o proporción de aspecto colocada primero o al final llega de borde a borde. La tarjeta quita el relleno de ese lado y recorta el contenido multimedia a sus esquinas.

import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function CardMedia() {
  return (
    <div className="grid w-full gap-4 sm:grid-cols-2">
      <Card>
        <img
          src="/preview/landscape.svg"
          alt="Mountain landscape"
          className="aspect-video w-full object-cover"
        />
        <CardHeader>
          <CardTitle>Media first</CardTitle>
          <CardDescription>
            The image runs edge to edge and inherits the card corners.
          </CardDescription>
        </CardHeader>
      </Card>
      <Card>
        <CardHeader>
          <CardTitle>Media last</CardTitle>
          <CardDescription>Bottom padding drops for the image.</CardDescription>
        </CardHeader>
        <img
          src="/preview/landscape.svg"
          alt="Mountain landscape"
          className="aspect-video w-full object-cover"
        />
      </Card>
    </div>
  )
}

Tarjeta clicable

Pon un <CardLink /> en el título para que toda la tarjeta sea un solo enlace. Los botones y otros controles de su interior siguen siendo clicables y conservan su propio foco, como la estrella de aquí.

"use client"

import * as React from "react"
import { IconGitBranch, IconStar } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardDescription,
  CardFooter,
  CardHeader,
  CardLink,
  CardTitle,
} from "@/components/ui/card"

const projects = [
  {
    name: "Acme website",
    description: "Marketing site and docs, deployed on every push.",
    stars: 128,
    branch: "main",
  },
  {
    name: "Payments service",
    description: "Card processing, webhooks and the reconciliation job.",
    stars: 42,
    branch: "release/2.4",
  },
]

export function CardClickable() {
  const [starred, setStarred] = React.useState<string[]>([])

  return (
    <div className="grid w-full gap-4 sm:grid-cols-2">
      {projects.map((project) => {
        const isStarred = starred.includes(project.name)

        return (
          <Card key={project.name}>
            <CardHeader>
              <CardTitle>
                <CardLink href="#">{project.name}</CardLink>
              </CardTitle>
              <CardDescription>{project.description}</CardDescription>
              <CardAction>
                <Button
                  variant="ghost"
                  size="icon-sm"
                  aria-label={`Star ${project.name}`}
                  aria-pressed={isStarred}
                  onClick={() =>
                    setStarred(
                      isStarred
                        ? starred.filter((name) => name !== project.name)
                        : [...starred, project.name]
                    )
                  }
                >
                  <IconStar className={isStarred ? "fill-current" : ""} />
                </Button>
              </CardAction>
            </CardHeader>
            <CardFooter>
              <span className="flex items-center gap-1 text-muted-foreground">
                <IconGitBranch className="size-4" />
                {project.branch}
              </span>
              <span className="ms-auto text-muted-foreground">
                {project.stars + (isStarred ? 1 : 0)} stars
              </span>
            </CardFooter>
          </Card>
        )
      })}
    </div>
  )
}

Variantes clicables

Cada variante recibe su propio feedback al pasar el cursor cuando contiene un enlace, y el anillo de foco envuelve toda la tarjeta cuando el enlace recibe foco con el teclado.

import { IconArrowUpRight } from "@tabler/icons-react"

import {
  Card,
  CardDescription,
  CardHeader,
  CardLink,
  CardTitle,
} from "@/components/ui/card"

export function CardLinkVariants() {
  return (
    <div className="grid w-full gap-4 sm:grid-cols-3">
      <Card size="sm">
        <CardHeader>
          <CardTitle>
            <CardLink href="#">
              Default <IconArrowUpRight className="inline size-4" />
            </CardLink>
          </CardTitle>
          <CardDescription>The shadow lifts on hover.</CardDescription>
        </CardHeader>
      </Card>
      <Card variant="outline" size="sm">
        <CardHeader>
          <CardTitle>
            <CardLink href="#">
              Outline <IconArrowUpRight className="inline size-4" />
            </CardLink>
          </CardTitle>
          <CardDescription>A soft fill on hover.</CardDescription>
        </CardHeader>
      </Card>
      <Card variant="muted" size="sm">
        <CardHeader>
          <CardTitle>
            <CardLink href="#">
              Muted <IconArrowUpRight className="inline size-4" />
            </CardLink>
          </CardTitle>
          <CardDescription>The fill deepens on hover.</CardDescription>
        </CardHeader>
      </Card>
    </div>
  )
}

Bordes

Añade border-b al encabezado o border-t al pie y la tarjeta añade el relleno correspondiente en el lado interior.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function CardBorders() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader className="border-b">
        <CardTitle>Notifications</CardTitle>
        <CardDescription>Choose what you hear about.</CardDescription>
      </CardHeader>
      <CardContent>
        <p>Mentions, replies and deploy failures are on.</p>
      </CardContent>
      <CardFooter className="border-t">
        <Button variant="outline">Reset</Button>
        <Button className="ms-auto">Save</Button>
      </CardFooter>
    </Card>
  )
}

Anidado

Una tarjeta dentro de CardContent deriva su radio del radio del padre menos el espaciado, de modo que las esquinas permanecen concéntricas.

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardLink,
  CardTitle,
} from "@/components/ui/card"

export function CardNested() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Usage</CardTitle>
        <CardDescription>This billing period.</CardDescription>
      </CardHeader>
      <CardContent>
        <Card variant="muted" size="sm">
          <CardHeader>
            <CardTitle>Build minutes</CardTitle>
            <CardDescription>1,240 of 3,000 used</CardDescription>
          </CardHeader>
        </Card>
        <Card variant="outline" size="sm">
          <CardHeader>
            <CardTitle>
              <CardLink href="#">Bandwidth</CardLink>
            </CardTitle>
            <CardDescription>84 GB of 100 GB used</CardDescription>
          </CardHeader>
        </Card>
      </CardContent>
    </Card>
  )
}

Contenido largo

Los títulos sin espacios, las URL largas, los emoji y el texto CJK se ajustan dentro de una tarjeta estrecha en lugar de ensancharla.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function CardLongContent() {
  return (
    <Card size="sm" className="w-72 max-w-full">
      <CardHeader>
        <CardTitle>
          Supercalifragilisticexpialidocious_unbroken_title_that_never_ends
        </CardTitle>
        <CardDescription>
          https://example.com/a/very/long/path/that/keeps/going/and/going?with=query
        </CardDescription>
        <CardAction>
          <Button variant="outline" size="sm">
            Edit
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <p>👩‍👩‍👧‍👦 你好世界,这是一个很长的中文句子用于测试换行</p>
      </CardContent>
      <CardFooter>
        <Button variant="outline">Cancel</Button>
        <Button>Continue</Button>
      </CardFooter>
    </Card>
  )
}

Semántica

Cada parte admite una prop render, así que una tarjeta puede ser un <article> con un encabezado, header y footer reales.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function CardSemantics() {
  return (
    <Card render={<article />} className="w-full max-w-sm">
      <CardHeader render={<header />}>
        <CardTitle render={<h3 />}>Rendered as an article</CardTitle>
        <CardDescription render={<p />}>
          Every part accepts render for real semantics.
        </CardDescription>
      </CardHeader>
      <CardFooter render={<footer />}>
        <Button variant="outline">Read more</Button>
      </CardFooter>
    </Card>
  )
}

De derecha a izquierda

La acción pasa al inicio en línea y el pie sigue la dirección de lectura.

import { IconDotsVertical } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardDescription,
  CardFooter,
  CardHeader,
  CardLink,
  CardTitle,
} from "@/components/ui/card"

export function CardRtl() {
  return (
    <div dir="rtl" className="w-full max-w-sm">
      <Card>
        <CardHeader>
          <CardTitle>
            <CardLink href="#">موقع أكمي</CardLink>
          </CardTitle>
          <CardDescription>
            موقع تسويقي ووثائق، يُنشر مع كل دفعة.
          </CardDescription>
          <CardAction>
            <Button variant="ghost" size="icon-sm" aria-label="المزيد">
              <IconDotsVertical />
            </Button>
          </CardAction>
        </CardHeader>
        <CardFooter>
          <Button variant="outline">إلغاء</Button>
          <Button>متابعة</Button>
        </CardFooter>
      </Card>
    </div>
  )
}

Una tarjeta no es enfocable por sí sola. Estas teclas se aplican cuando contiene un <CardLink />.

KeyAcción
TabDa foco al enlace de la tarjeta, que dibuja el anillo de foco alrededor de toda la tarjeta, y luego pasa a cada control de su interior.
EnterSigue al enlace de la tarjeta.
  • El nombre accesible del enlace es su propio texto, así que mantén <CardLink /> dentro del título en lugar de envolver toda la tarjeta en un ancla.
  • Los controles dentro de una tarjeta clicable se elevan por encima del área de clic del enlace, así que siguen siendo accesibles con el puntero y el teclado.
  • Usa render en <CardTitle /> para dar al título el nivel de encabezado que encaje en tu página.

Cada parte renderiza un <div> por defecto, acepta sus atributos y admite una prop render para cambiar el elemento.

PropTipoPredeterminado
variant
"default" | "outline" | "muted""default"
sizeEscala juntos el espaciado, el radio y el tamaño del título.
"default" | "sm" | "flush""default"
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="card"Apunta a la tarjeta en CSS.
data-variantLa variante actual.
data-sizeEl tamaño actual.
data-linkPresente mientras la tarjeta contiene un CardLink.
--card-spacingEspaciado interior compartido por todas las partes. Lo define size.
--card-radiusRadio de las esquinas. Las tarjetas anidadas derivan el suyo del padre.
--card-title-sizeTamaño de fuente usado por CardTitle.
PropTipoPredeterminado
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="card-header"Una cuadrícula que coloca CardAction en la esquina superior final.
PropTipoPredeterminado
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="card-title"Selecciona el título.
PropTipoPredeterminado
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="card-description"Selecciona la descripción.
PropTipoPredeterminado
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="card-action"Abarca las filas del título y la descripción, por encima del área de clic de un CardLink.
PropTipoPredeterminado
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="card-content"Pasa el radio y el espaciado de la tarjeta a las tarjetas anidadas.
PropTipoPredeterminado
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="card-footer"Selecciona el pie.
PropTipoPredeterminado
href
string–
renderPasa aquí el componente de enlace de tu router.
ReactElement | (props, state) => ReactElement<a>
AtributoDescripción
data-slot="card-link"Extiende su área de clic sobre toda la tarjeta.

Usado en bloques

Bloques que se construyen sobre Card.