# Folded Card

> Composable folded-corner card for React and shadcn/ui with a linked preview, dashed grid, badge, and footer, for template galleries.

Source: https://www.cult-ui.com/docs/components/folded-card

## Example

```tsx title="folded-card-demo.tsx"
"use client"

import { ArrowUpRight } from "lucide-react"

import {
  ChatTail,
  FoldedCard,
  FoldedCardBadge,
  FoldedCardBorder,
  FoldedCardDescription,
  FoldedCardFold,
  FoldedCardFooter,
  FoldedCardGrid,
  FoldedCardHeading,
  FoldedCardLink,
  FoldedCardPreview,
  FoldedCardTitle,
} from "@/components/ui/folded-card"

function UserIcon({ className }: { className?: string }) {
  return (
    <svg
      aria-hidden="true"
      className={className}
      fill="none"
      focusable="false"
      height="16"
      viewBox="0 0 16 16"
      width="16"
    >
      <path
        d="M5.125 5.875a2.875 2.875 0 1 1 5.75 0a2.875 2.875 0 0 1-5.75 0ZM2.75 13a4.5 4.5 0 0 1 9 0"
        stroke="currentColor"
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth="1.4"
      />
    </svg>
  )
}

function OpenAIIcon({ className }: { className?: string }) {
  return (
    <svg
      aria-hidden="true"
      className={className}
      fill="currentColor"
      focusable="false"
      height="14"
      viewBox="8 3 16 14"
      width="16"
    >
      <path d="M22.077 8.7301C22.399 7.7768 22.288 6.7325 21.773 5.8653C20.999 4.5353 19.443 3.851 17.923 4.173C17.247 3.4214 16.275 2.9939 15.257 3.0001C13.703 2.9966 12.325 3.9836 11.847 5.4422C10.848 5.6439 9.987 6.2604 9.483 7.1341C8.703 8.4606 8.881 10.1328 9.923 11.2703C9.601 12.2236 9.712 13.268 10.226 14.1351C11.001 15.4651 12.557 16.1494 14.077 15.8274C14.753 16.579 15.724 17.0065 16.743 16.9999C18.297 17.0039 19.676 16.016 20.154 14.556C21.152 14.3543 22.014 13.7379 22.518 12.8642C23.297 11.5376 23.119 9.8668 22.077 8.7292L22.077 8.7301ZM16.744 16.0851C16.122 16.086 15.519 15.8712 15.042 15.4778C15.063 15.4665 15.101 15.4459 15.125 15.431L17.951 13.821C18.095 13.74 18.184 13.5882 18.183 13.4242V9.494L19.377 10.1743C19.39 10.1805 19.398 10.1927 19.4 10.2067V13.4614C19.398 14.9086 18.21 16.082 16.744 16.0851ZM11.031 13.6775C10.719 13.1463 10.607 12.5238 10.714 11.9196C10.735 11.9318 10.772 11.9541 10.798 11.969L13.623 13.579C13.766 13.6617 13.944 13.6617 14.087 13.579L17.537 11.6137V12.9744C17.537 12.9884 17.531 13.002 17.52 13.0107L14.664 14.6378C13.392 15.3606 11.767 14.9309 11.032 13.6775H11.031ZM10.288 7.5922C10.598 7.0602 11.088 6.6533 11.671 6.442C11.671 6.466 11.67 6.5085 11.67 6.5382V9.7587C11.669 9.9223 11.758 10.0742 11.902 10.1551L15.351 12.1199L14.157 12.8003C14.145 12.8081 14.13 12.8095 14.117 12.8038L11.26 11.1754C9.991 10.45 9.556 8.8474 10.287 7.5926L10.288 7.5922ZM20.098 9.8449L16.649 7.8796L17.843 7.1997C17.855 7.1918 17.87 7.1905 17.883 7.1962L20.74 8.8233C22.011 9.5483 22.447 11.1535 21.712 12.4083C21.402 12.9394 20.912 13.3463 20.329 13.558V10.2413C20.33 10.0777 20.242 9.9263 20.098 9.8449H20.098ZM21.286 8.08C21.265 8.0673 21.229 8.0454 21.202 8.0305L18.377 6.4205C18.234 6.3378 18.057 6.3378 17.913 6.4205L14.464 8.3858V7.0252C14.463 7.0112 14.47 6.9976 14.48 6.9889L17.337 5.3618C18.608 4.6372 20.233 5.0669 20.968 6.3217C21.279 6.8528 21.391 7.4753 21.286 8.08ZM13.817 10.506L12.623 9.8252C12.61 9.8177 12.602 9.8046 12.6 9.7902V6.5382C12.601 5.0884 13.793 3.9132 15.261 3.9145C15.882 3.9145 16.485 4.1293 16.962 4.5227C16.941 4.534 16.904 4.5541 16.879 4.569L14.054 6.179C13.91 6.26 13.821 6.4118 13.822 6.5758L13.817 10.506ZM14.463 9.1328L16 8.25L17.537 9.1323V10.8968L16 11.7791L14.463 10.8968V9.1328Z" />
    </svg>
  )
}

function FoldedCardDemo({ href = "#" }: { href?: string }) {
  return (
    <FoldedCard>
      <FoldedCardFold />

      <FoldedCardLink href={href}>
        <FoldedCardBorder />

        <FoldedCardPreview>
          <FoldedCardGrid />

          <div className="absolute inset-0 flex items-center justify-center">
            <div className="relative mx-auto flex h-[168px] w-[224px] max-w-[256px] flex-col">
              <div className="relative flex h-full w-full flex-col">
                <div className="flex flex-col gap-5">
                  <div className="flex items-center gap-3">
                    <div className="flex h-8 w-8 items-center justify-center rounded-full border border-border bg-card text-muted-foreground shadow-sm">
                      <UserIcon className="h-4 w-4" />
                    </div>
                    <div className="relative h-8 w-[78px] rounded-full border border-border bg-card shadow-sm">
                      <ChatTail
                        className="absolute bottom-[-3px] left-[-6px] h-5 w-5"
                        isLight
                      />
                    </div>
                  </div>

                  <div className="flex items-center justify-end gap-3">
                    <div className="relative h-8 w-[108px] rounded-full border border-border bg-card shadow-sm">
                      <ChatTail
                        className="absolute right-[-6px] bottom-[-3px] h-5 w-5"
                        isLight
                        side="right"
                      />
                    </div>
                    <div className="flex h-8 w-8 items-center justify-center rounded-full border border-border bg-card text-foreground shadow-sm">
                      <OpenAIIcon className="h-4 w-4" />
                    </div>
                  </div>

                  <div className="relative mt-4 h-10 w-full rounded-lg border border-border bg-card shadow-sm">
                    <div className="absolute inset-y-1.5 right-1.5 flex w-6 items-center justify-center rounded-sm bg-primary text-[22px] text-primary-foreground leading-none">
                      <ArrowUpRight className="size-4" />
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </FoldedCardPreview>

        <FoldedCardFooter>
          <FoldedCardHeading>
            <FoldedCardTitle>Chatbot</FoldedCardTitle>
            <FoldedCardBadge>Next.js</FoldedCardBadge>
          </FoldedCardHeading>
          <FoldedCardDescription>
            A Next.js chatbot template featuring streaming text responses and a
            chat UI.
          </FoldedCardDescription>
        </FoldedCardFooter>
      </FoldedCardLink>
    </FoldedCard>
  )
}

export default FoldedCardDemo
```

Folded Card is a composable React card component for shadcn/ui. Use it for template galleries, blog indexes, or resource directories.

## Installation

### CLI

```bash
npx shadcn@latest add @cult-ui/folded-card
```

### Manual

**Copy and paste the following code into your project.**

```tsx title="folded-card.tsx"
"use client"

import { useId } from "react"
import Link from "next/link"

import { cn } from "@/lib/utils"

const FOLDED_CARD_CLIP_PATH =
  "polygon(0 8px, 8px 0, calc(100% - 40px) 0, 100% 40px, 100% calc(100% - 8px), calc(100% - 8px) 100%, 8px 100%, 0 calc(100% - 8px)"

function ChatTail({
  className,
  isLight = true,
  side = "left",
}: {
  className?: string
  isLight?: boolean
  side?: "left" | "right"
}) {
  const maskId = useId().replaceAll(":", "")
  const bubbleFill = isLight ? "var(--card)" : "var(--muted)"
  const bubbleStroke = "var(--border)"
  const mirrorTransform =
    side === "right" ? "translate(24 0) scale(-1 1)" : undefined
  return (
    <svg
      aria-hidden="true"
      className={className}
      fill="none"
      focusable="false"
      height="24"
      viewBox="0 0 24 24"
      width="24"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path d="M0 0H24V24H0z" fill="transparent" />
      <mask
        height="24"
        id={maskId}
        maskUnits="userSpaceOnUse"
        style={{ maskType: "alpha" }}
        width="24"
        x="0"
        y="0"
      >
        <path d="M0 0h24v24H0V0z" fill="#D9D9D9" />
      </mask>
      <g mask={`url(#${maskId})`} transform={mirrorTransform}>
        <path
          clipRule="evenodd"
          d="M27-19C15.954-19 7-10.046 7 1c0 .335.008.669.025 1H7v10a15 15 0 01-3 9c4.116 0 7.845-1.658 10.555-4.342A19.915 19.915 0 0027 21c11.046 0 20-8.954 20-20s-8.954-20-20-20z"
          fill={bubbleFill}
          fillRule="evenodd"
        />
        <path
          d="M7.025 2v1h1.05l-.052-1.05-.998.05zM7 2V1H6v1h1zm-3 19l-.8-.6L2 22h2v-1zm10.555-4.342l.623-.783-.695-.553-.631.625.703.71zM8 1c0-10.493 8.507-19 19-19v-2C15.402-20 6-10.598 6 1h2zm.023.95A19.327 19.327 0 018 1H6c0 .352.009.702.026 1.05l1.997-.1zM7 3h.025V1H7v2zm1 9V2H6v10h2zm-3.2 9.6A16 16 0 008 12H6a14 14 0 01-2.8 8.4l1.6 1.2zm9.052-5.653A13.952 13.952 0 014 20v2c4.39 0 8.37-1.77 11.259-4.632l-1.407-1.42zM27 20c-4.47 0-8.577-1.542-11.822-4.125l-1.245 1.565A20.915 20.915 0 0027 22v-2zM46 1c0 10.493-8.507 19-19 19v2c11.598 0 21-9.402 21-21h-2zM27-18c10.493 0 19 8.507 19 19h2c0-11.598-9.402-21-21-21v2z"
          fill={bubbleStroke}
        />
      </g>
    </svg>
  )
}

function FoldedCard({
  className,
  style,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      className={cn("relative mx-auto max-w-sm", className)}
      data-slot="folded-card"
      style={{ clipPath: FOLDED_CARD_CLIP_PATH, ...style }}
      {...props}
    />
  )
}

function FoldedCardFold({ className, ...props }: React.ComponentProps<"div">) {
  const gradientId = useId().replaceAll(":", "")

  return (
    <div
      className={cn(
        "pointer-events-none absolute top-0 right-0 z-30 h-10 w-10",
        className
      )}
      data-slot="folded-card-fold"
      {...props}
    >
      <svg
        aria-hidden="true"
        className="h-full w-full"
        fill="none"
        focusable="false"
        viewBox="0 0 40 40"
      >
        <defs>
          <linearGradient id={gradientId} x1="100%" x2="0%" y1="0%" y2="100%">
            <stop offset="0%" stopColor="var(--card)" />
            <stop offset="40%" stopColor="var(--muted)" />
            <stop offset="100%" stopColor="var(--accent)" />
          </linearGradient>
        </defs>
        <path d="M0 0 L40 40 L40 0 Z" fill={`url(#${gradientId})`} />
        <line
          stroke="var(--border)"
          strokeWidth="1"
          x1="0"
          x2="40"
          y1="0"
          y2="40"
        />
        <path
          d="M36 18 L36 4 L22 4"
          fill="none"
          stroke="var(--border)"
          strokeLinecap="round"
          strokeLinejoin="round"
          strokeWidth="1"
        />
      </svg>
      <div className="absolute inset-0 border-b border-l bg-muted" />
    </div>
  )
}

function FoldedCardLink({
  className,
  style,
  ...props
}: React.ComponentProps<typeof Link>) {
  return (
    <Link
      className={cn(
        "group relative block overflow-hidden bg-card no-underline transition-shadow hover:shadow-lg",
        className
      )}
      data-slot="folded-card-link"
      style={{ clipPath: FOLDED_CARD_CLIP_PATH, ...style }}
      {...props}
    />
  )
}

function FoldedCardBorder({
  className,
  style,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "pointer-events-none absolute inset-0 z-20 rounded-b-xl rounded-tl-xl",
        className
      )}
      data-slot="folded-card-border"
      style={{
        clipPath: FOLDED_CARD_CLIP_PATH,
        background: "var(--border)",
        padding: "1px",
        WebkitMask:
          "linear-gradient(#000,#000) content-box, linear-gradient(#000,#000)",
        WebkitMaskComposite: "xor",
        maskComposite: "exclude",
        ...style,
      }}
      {...props}
    />
  )
}

function FoldedCardPreview({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "relative h-[228px] overflow-hidden rounded-b-xl rounded-tl-xl bg-muted/40",
        className
      )}
      data-slot="folded-card-preview"
      {...props}
    />
  )
}

type FoldedCardGridProps = React.ComponentProps<"div"> & {
  rows?: number
  columns?: number
}

function FoldedCardGrid({
  className,
  rows = 4,
  columns = 4,
  ...props
}: FoldedCardGridProps) {
  const rowCount = Math.max(1, Math.floor(rows))
  const columnCount = Math.max(1, Math.floor(columns))

  return (
    <div
      className={cn("absolute inset-0", className)}
      data-slot="folded-card-grid"
      {...props}
    >
      {Array.from({ length: rowCount - 1 }, (_, index) => (
        <hr
          className="absolute w-full border border-border/30 border-t border-dashed"
          key={`row-${index + 1}`}
          style={{ top: `${((index + 1) / rowCount) * 100}%` }}
        />
      ))}

      {Array.from({ length: columnCount - 1 }, (_, index) => (
        <hr
          className="absolute h-full border border-border/30 border-l border-dashed"
          key={`column-${index + 1}`}
          style={{ left: `${((index + 1) / columnCount) * 100}%` }}
        />
      ))}
    </div>
  )
}

function FoldedCardFooter({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "flex flex-col gap-2 border-border border-t bg-card px-4 py-3",
        className
      )}
      data-slot="folded-card-footer"
      {...props}
    />
  )
}

function FoldedCardHeading({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      className={cn("flex items-center gap-2", className)}
      data-slot="folded-card-heading"
      {...props}
    />
  )
}

function FoldedCardTitle({ className, ...props }: React.ComponentProps<"h3">) {
  return (
    <h3
      className={cn("font-medium text-foreground text-sm", className)}
      data-slot="folded-card-title"
      {...props}
    />
  )
}

function FoldedCardBadge({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      className={cn(
        "rounded-full bg-primary px-2 py-0.5 font-medium text-primary-foreground text-xs",
        className
      )}
      data-slot="folded-card-badge"
      {...props}
    />
  )
}

function FoldedCardDescription({
  className,
  ...props
}: React.ComponentProps<"p">) {
  return (
    <p
      className={cn(
        "line-clamp-3 text-muted-foreground text-sm leading-relaxed",
        className
      )}
      data-slot="folded-card-description"
      {...props}
    />
  )
}

export {
  ChatTail,
  FoldedCard,
  FoldedCardFold,
  FoldedCardLink,
  FoldedCardBorder,
  FoldedCardPreview,
  FoldedCardGrid,
  FoldedCardFooter,
  FoldedCardHeading,
  FoldedCardTitle,
  FoldedCardBadge,
  FoldedCardDescription,
}
```

**Update the import paths to match your project setup.**
