Skip to content
Scrim UI

Source Card UI

A cited source as a card — favicon, page title, domain, and the snippet the answer actually drew from.

sourcecitationreference

Preview

Presets

Props

1
Agent promptClaude Code · Cursor · any agent
Add the Source Card UI component from the Scrim UI registry to this project and use it in the configuration described below.

Source Card UI — A cited source as a card — favicon, page title, domain, and the snippet the answer actually drew from.

## 1. Install

```bash
npx shadcn@latest add @scrimui/source-card
```

This writes a single file to `components/ui/source-card.tsx`. It is plain React + Tailwind with no runtime dependencies — no Radix, no CVA, nothing to add to package.json. If this project does not use the shadcn CLI, copy the source from https://scrimui.dev/components/source-card to the same path by hand; nothing in the file depends on shadcn.

## 2. Use it

```tsx
import { SourceCard } from "@/components/ui/source-card";

<SourceCard
  index={1}
  title="Claude Fable 5 and Mythos 5"
  url="https://www.anthropic.com/news/claude-fable-5-mythos-5"
  domain="anthropic.com"
  snippet="Fable 5 is the most advanced generally available Claude model; Mythos 5 is restricted to approved organizations."
/>
```

## 3. The configuration that matters

This is the component's "With snippet" state — Two lines of context help the reader judge relevance before clicking.

Every prop is at its default value. Keep the call site minimal — do not write out default values.

Reference: https://scrimui.dev/components/source-card

Follows the props below — change a control and the prompt changes with it, so an agent reproduces that configuration instead of the defaults.

Install

npx shadcn@latest add @scrimui/source-card

Component source

React + Tailwind component using your shadcn theme. No additional component dependencies.

source-card.tsx
"use client";

import * as React from "react";

/* ------------------------------------------------------------------ */
/* Types                                                               */
/* ------------------------------------------------------------------ */

export type SourceCardProps = {
  title: string;
  url: string;
  domain?: string;
  snippet?: string;
  favicon?: string;
  index?: number;
  className?: string;
};

/* ------------------------------------------------------------------ */
/* Helpers                                                             */
/* ------------------------------------------------------------------ */

function domainFromUrl(url: string) {
  try {
    return new URL(url).hostname.replace(/^www\./, "");
  } catch {
    return url;
  }
}

/* ------------------------------------------------------------------ */
/* SourceCard                                                          */
/* ------------------------------------------------------------------ */

export function SourceCard({
  title,
  url,
  domain,
  snippet,
  favicon,
  index,
  className = "",
}: SourceCardProps) {
  const host = domain ?? domainFromUrl(url);

  return (
    <a
      href={url}
      target="_blank"
      rel="noreferrer noopener"
      className={`group block rounded-xl border border-border shadow-sm bg-card p-4 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring hover:border-border hover:bg-muted ${className}`}
    >
      <div className="flex items-start gap-3">
        {favicon ? (
          // eslint-disable-next-line @next/next/no-img-element
          <img
            src={favicon}
            alt=""
            className="h-5 w-5 shrink-0 rounded"
            width={20}
            height={20}
          />
        ) : (
          <span
            className={`flex h-5 w-5 shrink-0 select-none items-center justify-center rounded text-xs font-medium bg-muted text-muted-foreground`}
          >
            {title.slice(0, 1).toUpperCase()}
          </span>
        )}
        <div className="min-w-0 flex-1">
          <p className="truncate text-sm font-medium text-foreground leading-5">
            {title}
          </p>
          <p className="mt-0.5 text-xs text-muted-foreground">{host}</p>
          {snippet && (
            <p className="mt-3 line-clamp-2 text-sm leading-5 text-muted-foreground">
              {snippet}
            </p>
          )}
        </div>
        {index !== undefined && (
          <span className="ml-auto flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted text-xs font-medium text-muted-foreground">
            {index}
          </span>
        )}
      </div>
    </a>
  );
}

When to use it

  • Always show the domain — it's the fastest relevance signal, before the title or snippet.
  • Number cards so the answer text can cite them with plain [1][2] markers.
  • Open in a new tab with rel="noreferrer noopener" — sources are third-party destinations.
  • Keep snippets to two lines; truncate rather than expanding the card.
  • Fall back to a colored letter avatar when a favicon isn't available — never a broken image icon.

What breaks in production

  • Hiding the source until hover — citations only work when the source is glanceable.
  • Showing favicon-less broken images; always have a graceful fallback.
  • Making the whole card a link that swallows the citation number as the anchor.
  • Dropping the snippet in a full-screen modal instead of an inline card.

Related Components