Website redesign
Updated 2 hours ago by Sam Rivera
In progress
Refresh the marketing site with the new brand and rebuild the pricing page.
18 of 28 tasks64%
x
APSRPSJK
import { ChevronRightIcon } from "lucide-react";
import { Avatar, AvatarFallback, AvatarGroup, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress";
import { people } from "@/examples/data";
export default function CardProject() {
return (
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Website redesign</CardTitle>
<CardDescription>Updated 2 hours ago by Sam Rivera</CardDescription>
<CardAction>
<Badge variant="info">In progress</Badge>
</CardAction>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<p className="text-muted-foreground">
Refresh the marketing site with the new brand and rebuild the pricing page.
</p>
<Progress value={64}>
<ProgressLabel>18 of 28 tasks</ProgressLabel>
<ProgressValue />
</Progress>
</CardContent>
<CardFooter>
<AvatarGroup>
{people.slice(0, 4).map((person) => (
<Avatar key={person.value} size="sm">
<AvatarImage src={person.image} alt={person.name} />
<AvatarFallback>{person.initials}</AvatarFallback>
</Avatar>
))}
</AvatarGroup>
<Button variant="ghost" size="sm" className="ml-auto">
Open
<ChevronRightIcon />
</Button>
</CardFooter>
</Card>
);
}- Add the registryOnce per project
pnpm dlx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json - Add card
pnpm dlx shadcn@latest add @jarvis/card
Usage
import { Button } from "@/components/ui/button";
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
export function ProjectCard() {
return (
<Card>
<CardHeader>
<CardTitle>Website redesign</CardTitle>
<CardDescription>Updated 2 hours ago</CardDescription>
<CardAction>
<Button variant="ghost" size="sm">
Edit
</Button>
</CardAction>
</CardHeader>
<CardContent>Refresh the marketing site with the new brand.</CardContent>
<CardFooter>
<Button variant="secondary" size="sm">
Open
</Button>
</CardFooter>
</Card>
);
}Every part is optional. CardAction sits at the top right of the header.
Composition
Examples
Form
Add border-b to the header to divide it from the content.
Invite a teammate
They’ll get an email with a link to join Acme.
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const roles = { viewer: "Viewer", member: "Member", admin: "Admin" };
export default function CardForm() {
return (
<Card className="w-full max-w-sm">
<CardHeader className="border-b">
<CardTitle>Invite a teammate</CardTitle>
<CardDescription>They’ll get an email with a link to join Acme.</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup className="gap-3">
<Field>
<FieldLabel htmlFor="invite-email">Email</FieldLabel>
<Input id="invite-email" type="email" placeholder="lena@example.com" />
</Field>
<Field>
<FieldLabel htmlFor="invite-role">Role</FieldLabel>
<Select items={roles} defaultValue="member">
<SelectTrigger id="invite-role" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(roles).map(([value, label]) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
</FieldGroup>
</CardContent>
<CardFooter className="justify-end">
<Button variant="ghost" size="sm">
Cancel
</Button>
<Button variant="primary" size="sm">
Send invite
</Button>
</CardFooter>
</Card>
);
}Small
size="sm" tightens the spacing.
Plan usage
Resets on April 1
Storage6.4 of 10 GB
Members12 of 25
import { EllipsisIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
export default function CardSmall() {
return (
<Card size="sm" className="w-full max-w-sm">
<CardHeader>
<CardTitle>Plan usage</CardTitle>
<CardDescription>Resets on April 1</CardDescription>
<CardAction>
<Button variant="ghost" size="icon-sm" aria-label="More options">
<EllipsisIcon />
</Button>
</CardAction>
</CardHeader>
<CardContent className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Storage</span>
<span className="tabular-nums">6.4 of 10 GB</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Members</span>
<span className="tabular-nums">12 of 25</span>
</div>
</CardContent>
</Card>
);
}Flat
variant="flat" swaps the shadow for a border.
Usage this month
Across all projects in Acme
Requests42%
x
Storage12%
x
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress";
export default function CardFlat() {
return (
<Card variant="flat" className="w-full max-w-sm">
<CardHeader>
<CardTitle>Usage this month</CardTitle>
<CardDescription>Across all projects in Acme</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Progress value={42}>
<ProgressLabel>Requests</ProgressLabel>
<ProgressValue />
</Progress>
<Progress value={12}>
<ProgressLabel>Storage</ProgressLabel>
<ProgressValue />
</Progress>
</CardContent>
</Card>
);
}Cover image
An image as the card's first child runs to its edges. Give a decorative image an empty alt.
Mobile app
The iOS and Android apps, built from one codebase.
Releasedv3.1.0
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Status } from "@/components/ui/status";
import { gradientImage } from "@/examples/data";
export default function CardCoverImage() {
return (
<Card className="w-full max-w-sm">
<img
src={gradientImage("#c3d4fd", "#b8e9c1", 320, 120)}
alt=""
className="aspect-[8/3] w-full object-cover"
/>
<CardHeader>
<CardTitle>Mobile app</CardTitle>
<CardDescription>The iOS and Android apps, built from one codebase.</CardDescription>
</CardHeader>
<CardFooter>
<Status variant="success">Released</Status>
<span className="ml-auto text-xs text-faint">v3.1.0</span>
</CardFooter>
</Card>
);
}Props
| Part | Extends |
|---|---|
| Card | <div> |
| CardHeader | <div> |
| CardTitle | <div> |
| CardDescription | <div> |
| CardAction | <div> |
| CardContent | <div> |
| CardFooter | <div> |
Card
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "default" |
variant
| "default" | "flat" | "default" |