Moodinary/UI

Card

Agrupar un tema con su acción. No anides cards dentro de cards.

Agrupa un dato o una tarea con título, contenido y acciones.

Instalación

shadcn
npx shadcn@latest add https://design.moodinary.com/r/card.json
import
import { Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter } from "@moodinary/ui/components/card";

Ejemplos

Card de ajustes

El patrón más común del panel: CardHeader con título y descripción, y el contenido en CardContent.

Alertas automáticas
Guardá una idea (“enojo”, “renuncias”) y te avisamos acá cuando aparezcan pulsos nuevos sobre ese tema. También podés pedírselo a Nubi con @Nubi en cualquier seguimiento.
Código
settings.tsx
import { Button } from "@moodinary/ui/components/button";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@moodinary/ui/components/card";
import { Input } from "@moodinary/ui/components/input";
import { Label } from "@moodinary/ui/components/label";
import { Select } from "@moodinary/ui/components/select";

export default function CardSettings() {
	return (
		<Card className="w-full">
			<CardHeader>
				<CardTitle>Alertas automáticas</CardTitle>
				<CardDescription>
					Guardá una idea (“enojo”, “renuncias”) y te avisamos acá cuando
					aparezcan pulsos nuevos sobre ese tema. También podés pedírselo a Nubi
					con @Nubi en cualquier seguimiento.
				</CardDescription>
			</CardHeader>
			<CardContent>
				<form
					className="flex flex-col gap-2 sm:flex-row"
					onSubmit={(event) => event.preventDefault()}
				>
					<div className="min-w-0 flex-1 space-y-2">
						<Label className="sr-only" htmlFor="card-alert-query">
							Idea a vigilar
						</Label>
						<Input
							id="card-alert-query"
							maxLength={200}
							placeholder="p. ej. enojo con los horarios"
						/>
					</div>
					<div className="space-y-2">
						<Label className="sr-only" htmlFor="card-alert-org">
							Organización
						</Label>
						<Select id="card-alert-org">
							<option>Organización de ejemplo</option>
						</Select>
					</div>
					<Button type="submit">Crear alerta</Button>
				</form>
			</CardContent>
		</Card>
	);
}

Resumen

Métrica principal con CardAction para el cambio.

Participación
+6 %
82 %312 de 380

Datos ilustrativos.

Código
summary.tsx
import { Badge } from "@moodinary/ui/components/badge";
import {
	Card,
	CardAction,
	CardContent,
	CardHeader,
	CardTitle,
} from "@moodinary/ui/components/card";

export default function CardSummary() {
	return (
		<Card className="w-full max-w-sm">
			<CardHeader>
				<CardTitle>Participación</CardTitle>
				<CardAction>
					<Badge variant="ok">+6 %</Badge>
				</CardAction>
			</CardHeader>
			<CardContent>
				<div className="flex items-baseline gap-2">
					<b className="font-semibold text-3xl tabular-nums">82 %</b>
					<span className="text-ink-3">312 de 380</span>
				</div>
				<p className="mt-2 text-ink-3 text-xs">Datos ilustrativos.</p>
			</CardContent>
		</Card>
	);
}

Con acciones

CardFooter con borde lleva las acciones a la derecha.

Informe mensual
Septiembre listo para revisar. Informe ilustrativo.
Código
report.tsx
import { Button } from "@moodinary/ui/components/button";
import {
	Card,
	CardContent,
	CardFooter,
	CardHeader,
	CardTitle,
} from "@moodinary/ui/components/card";
import {
	Dialog,
	DialogContent,
	DialogDescription,
	DialogHeader,
	DialogTitle,
	DialogTrigger,
} from "@moodinary/ui/components/dialog";

export default function CardReport() {
	return (
		<Card className="w-full max-w-sm">
			<CardHeader>
				<CardTitle>Informe mensual</CardTitle>
			</CardHeader>
			<CardContent className="text-ink-2">
				Septiembre listo para revisar. Informe ilustrativo.
			</CardContent>
			<CardFooter className="justify-end border-t">
				<Dialog>
					<DialogTrigger
						render={
							<Button size="sm" variant="ghost">
								Vista previa
							</Button>
						}
					/>
					<DialogContent>
						<DialogHeader>
							<DialogTitle>Informe de ejemplo</DialogTitle>
							<DialogDescription>
								Acá aparecería el resumen del período elegido.
							</DialogDescription>
						</DialogHeader>
					</DialogContent>
				</Dialog>
				<Dialog>
					<DialogTrigger render={<Button size="sm">Enviar</Button>} />
					<DialogContent>
						<DialogHeader>
							<DialogTitle>Envío de ejemplo</DialogTitle>
							<DialogDescription>
								Este ejemplo no envía correos ni comparte datos.
							</DialogDescription>
						</DialogHeader>
					</DialogContent>
				</Dialog>
			</CardFooter>
		</Card>
	);
}

Cargando

Skeletons con la forma del contenido final.

Cargando resumen…
Código
loading.tsx
import { Card, CardContent, CardHeader } from "@moodinary/ui/components/card";
import { Skeleton } from "@moodinary/ui/components/skeleton";

export default function CardLoading() {
	return (
		<Card
			aria-busy="true"
			aria-label="Cargando resumen"
			className="w-full max-w-sm"
		>
			<CardHeader>
				<Skeleton className="h-3.5 w-2/5" />
			</CardHeader>
			<CardContent className="grid gap-3">
				<Skeleton className="h-[26px] w-[55%]" />
				<Skeleton className="h-2.5 w-4/5" />
				<span className="sr-only" role="status">
					Cargando resumen…
				</span>
			</CardContent>
		</Card>
	);
}

Props

Card

PropTipoDefaultDescripción
size"default" | "sm""default"Padding compacto en sm.
noiseboolean-Textura sutil de fondo.
classNamestring-Clases extra.

CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter

PropTipoDefaultDescripción
...propsComponentProps<"div">-Partes de la card.
classNamestring-Clases extra.

Cuándo sí y cuándo no

Hacé
  • Un tema por card.
  • Poné la acción principal en el footer.
  • Usá skeletons con la forma real.
Evitá
  • No anides cards.
  • No pongas filas de tres cards iguales como relleno.
  • No uses spinners para cargar una card.

Accesibilidad

  • El título visible identifica la card.
  • Mientras carga, usá aria-busy.