# Card (Notchset): prompt.md (v1.0.0)

- id: `card` · version 1.0.0 · component · free
- category: Layout
- build: Base UI (one set of files; its dependencies follow the build)
- install (this build): `npx shadcn@latest add https://notchset.dev/r/card.json`
- npm dependencies: none
- registry dependencies: utils, https://notchset.dev/r/notchset-foundation.json
- docs: https://notchset.dev/components/card
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

shadcn's Card in Notchset: a 1px rule border, square, no shadow, with the same parts. Compact density tightens it; selected turns the border ink with a corner notch; loading keeps the content's size under skeleton bars so nothing shifts; CardLink makes the whole card one link while controls marked data-above stay clickable.

## Build it
- Stack: React 19 (`ref` is a plain prop), TypeScript, Tailwind CSS v4 utilities, a shadcn-initialised project with the `@/*` alias.
- Packages: none beyond React.
- Files: `components/ui/notchset/card.tsx`.
- Registry dependencies, installed with it automatically: shadcn `utils` (cn), `notchset-foundation`.
- Builds: one set of files for both, but its dependencies come in Base UI and Radix builds. Install the one that matches the project (see Install): a free item's bare URL installs the Base UI build of it and its dependencies.
- Exports to keep: `Card`, `CardAction`, `CardContent`, `CardDescription`, `CardFooter`, `CardHeader`, `CardLink`, `CardTitle`, `CardValue`, and every exported type.
- CSS: the install adds the notchset foundation (tokens, keyframes, motion levels) to your global stylesheet once. Nothing to import by hand.
- Re-running `add` (or `--overwrite`) re-applies those rules: put overrides in your own CSS, never in the installed rules.
- Tokens: retheme with the `--notchset-*` custom properties (`--notchset-check`, `--notchset-control-edge`, `--notchset-draw-from`, `--notchset-ease-bloom`, `--notchset-ease-glide`, `--notchset-ease-key-down`, `--notchset-ease-key-up`, `--notchset-ease-travel`, `--notchset-fade`, `--notchset-focus-color`, `--notchset-focus-inset`, `--notchset-grow-to`, `--notchset-key-down`, `--notchset-key-up`, `--notchset-life-from`, `--notchset-life-ms`, `--notchset-node-blink`, `--notchset-node-bloom`, `--notchset-node-delay`, `--notchset-plate-color`, `--notchset-rise-from`, `--notchset-rule`, `--notchset-scan-to`, `--notchset-scroll`, `--notchset-sheet-from`, `--notchset-signal`, `--notchset-sweep-to`, `--notchset-travel`). Never add Tailwind colour classes inside the component.

```tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardValue } from "@/components/ui/notchset/card";
```

## Parts

| Part | data-slot | What it is for |
|---|---|---|
| `Card` | `card` | The box; density, selected, loading. |
| `CardHeader / CardTitle / CardDescription / CardAction` | `card-header` | Title, a line under it, something top-right. |
| `CardContent / CardFooter` | `card-content` | Body and actions row. |
| `CardValue` | `card-value` | A big mono number. |
| `CardLink` | `card-link` | Makes the whole card one link. |

Style a part with `[data-slot="<slot>"]` selectors or its `className`; keep the attributes when editing.

## Sound
- Keep every `data-slot` and `data-sound` attribute: the sound layer reads them.
- Installing this item adds no audio. Nothing plays until the app mounts `SoundProvider` once (install: `npx shadcn@latest add https://notchset.dev/r/notchset-sound.json`, import from `@/components/ui/notchset/sound-provider`); `useSound()` gives `muted` and `setMuted` for a mute control. Without a provider the audio engine never loads.

## Match the original
- Read `components/ui/notchset/card.tsx` as the reference implementation before changing or recreating anything, and match it: sizes, colours per theme, motion timings, copy and behaviour.
- If you deviate (a prop you can't honour, a style you changed, a dependency you swapped), say so in your reply, part by part.
- Keep the accessibility contract, the keyboard map and the motion levels listed below.

## Use it when
- card, panel, tile, plan card, summary card, clickable card, Notchset
- Grouping one subject: a plan, a run, a usage summary
- Tiles in a grid that each open something

### Not when
- Every section of a page (rules and spacing are enough)
- Lists of many rows: use a table

## Mistakes
- Don't nest links inside a CardLink card; give other controls data-above
- Keep loading cards' content mounted so heights hold

## Usage

```tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardValue } from "@/components/ui/notchset/card";

export function Usage() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Usage this month</CardTitle>
        <CardDescription>Resets on Nov 1</CardDescription>
      </CardHeader>
      <CardContent><CardValue>31,406</CardValue></CardContent>
    </Card>
  );
}
```

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `density` | `'default' \| 'compact'` |  | Padding, title and value size. |
| `selected` | `boolean` |  | Ink border and a corner notch (for cards that are options). |
| `loading` | `boolean` |  | Hides the content in place under skeleton bars; aria-busy. |

Full docs: https://notchset.dev/components/card

## Customising
- Colours: the component reads your shadcn tokens (`--background`, `--foreground`, `--border` …), refined by the `--notchset-*` tokens. The signal colour is `--notchset-signal` (it falls back to `--destructive`). Set tokens on `:root`, or on any container to retheme one area.
- Dark mode follows the `.dark` class on an ancestor (the shadcn and next-themes convention).
- Update later by re-running the install with `--overwrite` (review the diff if you edited it). Changelog: https://notchset.dev/r/changelog.json

## Keyboard

| Keys | Action |
|---|---|
| Tab | Reaches the card's link and its own buttons |

## Performance

- No client code: works in Server Components.

## Responsive

- Fills its grid cell; put cards in an auto-fit grid.

## Motion inventory

| Interaction | What moves |
|---|---|
| Select | Border fades to ink (160ms) |
| Loading | A line sweeps across (1500ms) |

## Accessibility contract (preserve when editing)
- A loading card is aria-busy
- With CardLink the whole card is one link named by its title; inner buttons stay separate
- Cards used as options take role radio from you (see the demo)

## Install

Base UI project (a base-* style in components.json):

```bash
npx shadcn@latest add https://notchset.dev/r/card.json
```

Radix project (a radix-*, new-york or default style):

```bash
npx shadcn@latest add https://notchset.dev/r/radix-nova/card.json
```

Or add the `@notchset` registry to components.json and run `npx shadcn@latest add @notchset/card`: the CLI picks the build from your style.

## Credits
- Built on shadcn/ui (https://ui.shadcn.com)

## Source (Base UI build)

### components/ui/notchset/card.tsx

```tsx
/**
 * Card (Notchset) v1.0.0 · Notchset
 * Docs: https://notchset.dev/components/card
 * MIT licensed: free to use, change and share.
 */
import * as React from "react";
import { cn } from "@/lib/utils";

/*
 * Notchset Card (one build: no primitive). shadcn's Card, same parts, in Notchset: a 1px rule border on
 * the page's background, square, no shadow. density="compact" tightens the padding, title and value.
 * selected turns the border ink and adds a 5px notch at the top-left corner. loading keeps the content's
 * size (hidden, not removed) under skeleton bars and a sweeping line, so nothing shifts when it lands.
 * CardLink makes the whole card one link; anything marked data-above (a COPY ID button) stays clickable.
 */

type Density = "default" | "compact";

function Card({
  density = "default",
  selected = false,
  loading = false,
  className,
  children,
  ...props
}: React.ComponentProps<"div"> & { density?: Density; selected?: boolean; loading?: boolean }) {
  return (
    <div
      data-slot="card"
      data-density={density}
      data-selected={selected || undefined}
      aria-busy={loading || undefined}
      className={cn(
        "group/card relative flex min-w-0 flex-col gap-(--card-gap) border border-solid bg-card py-(--card-pad-y) text-card-foreground transition-[border-color,background-color] duration-[160ms] ease-linear",
        "[--card-gap:14px] [--card-pad-x:20px] [--card-pad-y:20px] data-[density=compact]:[--card-gap:8px] data-[density=compact]:[--card-pad-x:14px] data-[density=compact]:[--card-pad-y:12px]",
        // Controls marked data-above stay clickable over a CardLink's overlay.
        "[&_[data-above]]:relative [&_[data-above]]:z-[1]",
        selected ? "border-foreground" : "border-[var(--notchset-rule,var(--border))]",
        className,
      )}
      {...props}
    >
      {selected && <span aria-hidden data-slot="card-notch" className="absolute -start-[3px] -top-[3px] size-[5px] bg-foreground" />}
      {loading ? (
        <>
          <div className="invisible contents">{children}</div>
          <CardSkeleton />
        </>
      ) : (
        children
      )}
    </div>
  );
}

/** Skeleton bars over the hidden content, and a line sweeping across. */
function CardSkeleton() {
  return (
    <div aria-hidden data-slot="card-skeleton" className="pointer-events-none absolute inset-0 flex flex-col gap-3 overflow-hidden px-(--card-pad-x) py-(--card-pad-y)">
      <span className="h-3.5 w-[45%] bg-[var(--notchset-rule,var(--border))]" />
      <span className="h-2.5 w-[80%] bg-[var(--notchset-rule,var(--border))]" />
      <span className="h-2.5 w-[60%] bg-[var(--notchset-rule,var(--border))]" />
      <span className="absolute inset-y-0 w-px bg-foreground opacity-50 motion-safe:animate-[notchset-sweep_1500ms_var(--notchset-ease-glide)_infinite]" />
    </div>
  );
}

function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-header"
      className={cn("grid auto-rows-min grid-cols-[minmax(0,1fr)] items-start gap-1 px-(--card-pad-x) has-data-[slot=card-action]:grid-cols-[minmax(0,1fr)_auto]", className)}
      {...props}
    />
  );
}

function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  return <div data-slot="card-title" className={cn("text-[17px] leading-snug font-semibold group-data-[density=compact]/card:text-[15px]", className)} {...props} />;
}

function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
  return <div data-slot="card-description" className={cn("text-[14px] leading-[1.45] text-muted-foreground", className)} {...props} />;
}

/** Top-right of the header: a badge or a small action. */
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-pad-x)", className)} {...props} />;
}

function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
  return <div data-slot="card-footer" className={cn("flex flex-wrap items-center gap-2 px-(--card-pad-x)", className)} {...props} />;
}

/** A big mono number: 30px light, 22px compact. */
function CardValue({ className, ...props }: React.ComponentProps<"div">) {
  return <div data-slot="card-value" className={cn("font-mono text-[30px] leading-none font-light tracking-[-0.03em] tabular-nums group-data-[density=compact]/card:text-[22px]", className)} {...props} />;
}

/**
 * Makes the whole card one link: its ::after covers the card. Put it around the title. Controls that
 * must stay clickable on their own (COPY ID) take data-above.
 */
function CardLink({ className, ...props }: React.ComponentProps<"a">) {
  return (
    <a
      data-slot="card-link"
      className={cn(
        `notchset-focus`, "text-inherit no-underline after:absolute after:inset-0 after:content-[''] hover:underline group-has-[[data-above]:hover]/card:no-underline [--notchset-focus-inset:-4px]",
        className,
      )}
      {...props}
    />
  );
}

export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardLink, CardTitle, CardValue };
```

The notchset foundation (the tokens listed above, keyframes and motion levels) installs once with the first component; its CSS is public at https://notchset.dev/r/notchset-foundation.json.
