Card
Una tarjeta plana separada por tono, no por bordes, con los roles de título, meta y cuerpo del sistema.
Live
Online store
Q 48.2k
Instalación
$ pnpm dlx shadcn@latest add @ssntl/cardUso
import { Card, CardBody, CardSpacer, CardTitle, CardWell } from "@/components/ui/card"
<Card tone="ground">
<CardWell><Store /></CardWell>
<CardSpacer />
<CardTitle>Tienda en línea</CardTitle>
<CardBody>Órdenes, pagos e inventario en un solo lugar.</CardBody>
</Card>- El tono separa, nunca los bordes: muted dentro de una superficie blanca, ground sobre la página, una sola tarjeta widget por grid.
- Los roles tipográficos del sistema: título text-heading, meta text-label, cuerpo text-body.
- El contenido define la altura; las tarjetas de una fila se estiran a la más alta para alinear sus bordes inferiores.
Código
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
// Essential Design `es-card`: a flat tile separated by tone, never by borders.
// `radius-lg`, tight padding so the content fills it, content sets the height.
const cardVariants = cva(
"relative flex min-w-0 flex-col gap-2 rounded-lg text-foreground",
{
variants: {
tone: {
// Inside a white surface.
muted: "bg-muted",
// Directly on the page background.
ground: "bg-card",
// The one dark tile of a bento grid, in both themes.
widget: "bg-black text-neutral-50 shadow-edge",
},
size: {
sm: "rounded-md p-5",
md: "p-6",
lg: "gap-3 p-8",
},
},
defaultVariants: { tone: "muted", size: "md" },
}
)
function Card({
tone,
size,
className,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof cardVariants>) {
return (
<div
data-slot="card"
data-tone={tone ?? "muted"}
className={cn(cardVariants({ tone, size, className }))}
{...props}
/>
)
}
/** A 44px round icon well at the top of the card. */
function CardWell({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-well"
className={cn(
"grid size-11 shrink-0 place-items-center rounded-full bg-secondary text-foreground [&_svg]:size-5",
"in-data-[tone=widget]:bg-card in-data-[tone=widget]:text-foreground",
className
)}
{...props}
/>
)
}
/** Pushes the content to the bottom of the card (at least spacing-4). */
function CardSpacer({ className, ...props }: React.ComponentProps<"div">) {
return (
<div aria-hidden className={cn("min-h-4 flex-1", className)} {...props} />
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"h3">) {
return (
<h3
data-slot="card-title"
className={cn("text-heading", className)}
{...props}
/>
)
}
function CardMeta({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="card-meta"
className={cn(
"text-label font-normal text-muted-foreground in-data-[tone=widget]:text-neutral-300",
className
)}
{...props}
/>
)
}
function CardBody({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="card-body"
className={cn(
"text-body text-pretty text-muted-foreground in-data-[tone=widget]:text-neutral-300",
className
)}
{...props}
/>
)
}
export {
Card,
CardBody,
CardMeta,
CardSpacer,
CardTitle,
CardWell,
cardVariants,
}