cortexcn

Card

Displays a card with header, content, and footer.

Loading…
import { Button } from "@/components/ui/button";import {  Card,  CardAction,  CardContent,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from "@/components/ui/card";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";export default function CardDemo() {  return (    <Card className="w-full max-w-sm">      <CardHeader>        <CardTitle>Login to your account</CardTitle>        <CardDescription>          Enter your email below to login to your account        </CardDescription>        <CardAction>          <Button variant="link">Sign Up</Button>        </CardAction>      </CardHeader>      <CardContent>        <form>          <div className="flex flex-col gap-6">            <div className="grid gap-2">              <Label htmlFor="email">Email</Label>              <Input                id="email"                type="email"                placeholder="m@example.com"                required              />            </div>            <div className="grid gap-2">              <div className="flex items-center">                <Label htmlFor="password">Password</Label>                <a                  href="#"                  className="ml-auto inline-block text-sm underline-offset-4 hover:underline"                >                  Forgot your password?                </a>              </div>              <Input id="password" type="password" required />            </div>          </div>        </form>      </CardContent>      <CardFooter className="flex-col gap-2">        <Button type="submit" className="w-full">          Login        </Button>        <Button variant="outline" className="w-full">          Login with Google        </Button>      </CardFooter>    </Card>  );}

Installation

npx shadcn@latest add @cortexcn/card
Copy and paste the following code into your project.
components/ui/card.tsx
import * as React from "react";import { cn } from "@/lib/utils";function Card({  className,  size = "default",  ...props}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {  return (    <div      data-slot="card"      data-size={size}      className={cn(        "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",        className,      )}      {...props}    />  );}function CardHeader({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="card-header"      className={cn(        "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",        className,      )}      {...props}    />  );}function CardTitle({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="card-title"      className={cn("font-semibold leading-none", className)}      {...props}    />  );}function CardDescription({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="card-description"      className={cn("text-sm text-muted-foreground", className)}      {...props}    />  );}function CardAction({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="card-action"      className={cn(        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",        className,      )}      {...props}    />  );}function CardContent({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="card-content"      className={cn("px-(--card-spacing)", className)}      {...props}    />  );}function CardFooter({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="card-footer"      className={cn(        "flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",        className,      )}      {...props}    />  );}export {  Card,  CardHeader,  CardFooter,  CardTitle,  CardAction,  CardDescription,  CardContent,};
Update the import paths to match your project setup.

Usage

import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
    <CardDescription>Card Description</CardDescription>
    <CardAction>Card Action</CardAction>
  </CardHeader>
  <CardContent>
    <p>Card Content</p>
  </CardContent>
  <CardFooter>
    <p>Card Footer</p>
  </CardFooter>
</Card>

Composition

Use the following composition to build a Card:

Card
├── CardHeader
│   ├── CardTitle
│   ├── CardDescription
│   └── CardAction
├── CardContent
└── CardFooter

Examples

Size

Use the size="sm" prop to set the card to the compact size variant.

Loading…
import { Button } from "@/components/ui/button";import {  Card,  CardContent,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from "@/components/ui/card";export default function CardSmall() {  return (    <Card size="sm" className="mx-auto w-full max-w-sm">      <CardHeader>        <CardTitle>Small Card</CardTitle>        <CardDescription>          This card uses the small size variant.        </CardDescription>      </CardHeader>      <CardContent>        <p>          The card component supports a size prop that can be set to          &quot;sm&quot; for a more compact appearance.        </p>      </CardContent>      <CardFooter>        <Button variant="outline" size="sm" className="w-full">          Action        </Button>      </CardFooter>    </Card>  );}

Image

Add an image as the first child of the card to create a card with a cover image.

Loading…
import { Badge } from "@/components/ui/badge";import { Button } from "@/components/ui/button";import {  Card,  CardAction,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from "@/components/ui/card";export default function CardImage() {  return (    <Card className="relative mx-auto w-full max-w-sm pt-0">      <div className="absolute inset-0 z-30 aspect-video bg-black/35" />      {/* eslint-disable-next-line @next/next/no-img-element */}      <img        src="https://avatar.vercel.sh/shadcn1"        alt="Event cover"        className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale dark:brightness-40"      />      <CardHeader>        <CardAction>          <Badge variant="secondary">Featured</Badge>        </CardAction>        <CardTitle>Design systems meetup</CardTitle>        <CardDescription>          A practical talk on component APIs, accessibility, and shipping          faster.        </CardDescription>      </CardHeader>      <CardFooter>        <Button className="w-full">View Event</Button>      </CardFooter>    </Card>  );}

Edge to Edge

Use negative margins with -mx-(--card-spacing) to make content go edge to edge while staying aligned with the card inset.

Loading…
import { Button } from "@/components/ui/button";import {  Card,  CardContent,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from "@/components/ui/card";export default function CardEdgeToEdge() {  return (    <Card className="mx-auto w-full max-w-sm">      <CardHeader>        <CardTitle>Terms of Service</CardTitle>        <CardDescription>          Review the terms before accepting the agreement.        </CardDescription>      </CardHeader>      <CardContent className="-mb-(--card-spacing)">        <div className="-mx-(--card-spacing) max-h-48 space-y-4 overflow-y-scroll border-t bg-muted/50 px-(--card-spacing) py-4 text-sm leading-relaxed">          <p>            These terms govern your use of the workspace, including access to            shared documents, project files, and collaboration tools.          </p>          <p>            You are responsible for the content you upload and for ensuring that            your team has the appropriate permissions to view or edit it.          </p>          <p>            We may update features or limits as the service evolves. When those            changes materially affect your workflow, we will notify your            workspace administrators.          </p>          <p>            By continuing, you agree to keep your account credentials secure and            to follow your organization&apos;s acceptable use policies.          </p>        </div>      </CardContent>      <CardFooter className="justify-end gap-2">        <Button variant="outline">Decline</Button>        <Button>Accept</Button>      </CardFooter>    </Card>  );}

Spacing

Use the --card-spacing CSS variable to control the spacing between sections and the inset of card parts.

Loading…
"use client";import * as React from "react";import { Button } from "@/components/ui/button";import {  Card,  CardAction,  CardContent,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from "@/components/ui/card";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";const spacingOptions = [  { className: "[--card-spacing:--spacing(4)]", label: "16px", value: "4" },  { className: "[--card-spacing:--spacing(5)]", label: "20px", value: "5" },  { className: "[--card-spacing:--spacing(6)]", label: "24px", value: "6" },  { className: "[--card-spacing:--spacing(8)]", label: "32px", value: "8" },];export default function CardSpacing() {  const [spacing, setSpacing] = React.useState("4");  const selectedSpacing = spacingOptions.find((o) => o.value === spacing);  return (    <div className="mx-auto grid w-full max-w-sm gap-4">      <ToggleGroup        type="single"        value={spacing}        onValueChange={(value) => {          if (value) setSpacing(value);        }}        variant="outline"        size="sm"        className="justify-center"      >        {spacingOptions.map((option) => (          <ToggleGroupItem key={option.value} value={option.value}>            {option.label}          </ToggleGroupItem>        ))}      </ToggleGroup>      <Card className={selectedSpacing?.className}>        <CardHeader>          <CardTitle>Login to your account</CardTitle>          <CardDescription>            Enter your email below to login to your account          </CardDescription>          <CardAction>            <Button variant="link">Sign Up</Button>          </CardAction>        </CardHeader>        <CardContent>          <form>            <div className="flex flex-col gap-6">              <div className="grid gap-2">                <Label htmlFor="email-spacing">Email</Label>                <Input                  id="email-spacing"                  type="email"                  placeholder="m@example.com"                  required                />              </div>              <div className="grid gap-2">                <Label htmlFor="password-spacing">Password</Label>                <Input id="password-spacing" type="password" required />              </div>            </div>          </form>        </CardContent>        <CardFooter className="flex-col gap-2">          <Button type="submit" className="w-full">            Login          </Button>        </CardFooter>      </Card>    </div>  );}

RTL

Cards respect the document direction — pass dir="rtl" to flip the layout.

Loading…
"use client";import * as React from "react";import { Button } from "@/components/ui/button";import {  Card,  CardAction,  CardContent,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from "@/components/ui/card";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";const content = {  ltr: {    dir: "ltr" as const,    title: "Login to your account",    description: "Enter your email below to login to your account",    signUp: "Sign Up",    email: "Email",    password: "Password",    login: "Login",  },  rtl: {    dir: "rtl" as const,    title: "تسجيل الدخول إلى حسابك",    description: "أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك",    signUp: "إنشاء حساب",    email: "البريد الإلكتروني",    password: "كلمة المرور",    login: "تسجيل الدخول",  },};export default function CardRtl() {  const [mode, setMode] = React.useState<"rtl" | "ltr">("rtl");  const t = content[mode];  return (    <div className="flex flex-col items-center gap-6">      <Button        variant="ghost"        size="sm"        onClick={() => setMode((m) => (m === "rtl" ? "ltr" : "rtl"))}      >        Toggle dir ({mode})      </Button>      <Card className="w-full max-w-sm" dir={t.dir}>        <CardHeader>          <CardTitle>{t.title}</CardTitle>          <CardDescription>{t.description}</CardDescription>          <CardAction>            <Button variant="link">{t.signUp}</Button>          </CardAction>        </CardHeader>        <CardContent>          <div className="flex flex-col gap-6">            <div className="grid gap-2">              <Label htmlFor="email-rtl">{t.email}</Label>              <Input id="email-rtl" type="email" placeholder="m@example.com" />            </div>            <div className="grid gap-2">              <Label htmlFor="password-rtl">{t.password}</Label>              <Input id="password-rtl" type="password" />            </div>          </div>        </CardContent>        <CardFooter>          <Button type="submit" className="w-full">            {t.login}          </Button>        </CardFooter>      </Card>    </div>  );}

API Reference

Card

PropTypeDefault
size"default" | "sm""default"

The card spacing can also be customized via the --card-spacing CSS variable, e.g. className="[--card-spacing:--spacing(6)]".

CardHeader, CardTitle, CardDescription, CardAction, CardContent, and CardFooter each accept a className for further customization.

On this page