Context Cards
Retrieved knowledge chunks with their sources.
"use client";
/*
* MIT License
*
* Copyright (c) 2026 Shane Levine
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import "./styles/foundation.css";
import { useEffect, useState } from "react";
/* ─────────────────────────────────────────────────────────
* CONTEXT CARDS
* Retrieved chunks enter once, then remain available.
* ───────────────────────────────────────────────────────── */
export type ContextChunk = {
title: string;
chars: string;
body: string;
source: string;
badge: string;
tone: string;
};
export type ContextCardsLabels = {
header: string;
count: string;
};
const DEFAULT_LABELS: ContextCardsLabels = {
header: "All chunks",
count: "32",
};
const CHUNKS: ContextChunk[] = [
{
title: "Vendor onboarding rule",
chars: "290 characters",
body: "Cold-chain certification must be verified before a new dairy can be added to the reorder workflow.",
source: "Dairy Onboarding SOP.pdf",
badge: "PDF",
tone: "bg-red",
},
{
title: "Seasonal demand row",
chars: "1,250 characters",
body: "Q4 velocity table: pistachio +18%, vanilla +6%, rocky road -11%; retire projects below 40 works weekly.",
source: "Sales Velocity Export.csv",
badge: "CSV",
tone: "bg-green",
},
];
export default function ContextCards({
chunks = CHUNKS,
labels,
className,
}: {
/** Accepted for gallery/registry parity; ContextCards has no visual variants. */
variant?: string;
chunks?: ContextChunk[];
labels?: Partial<ContextCardsLabels>;
className?: string;
} = {}) {
const [chipsShown, setChipsShown] = useState(false);
const copy = { ...DEFAULT_LABELS, ...labels };
useEffect(() => {
const chips = setTimeout(() => setChipsShown(true), 700);
return () => clearTimeout(chips);
}, []);
return (
<div className={`flex w-full max-w-95 flex-col gap-2${className ? ` ${className}` : ""}`}>
<div
className="flex items-center gap-2 px-0.5"
style={{ animation: "fade-in 400ms ease-out both" }}
>
<span className="text-[13px] font-semibold text-ink">{copy.header}</span>
<span className="inline-flex h-5 items-center rounded-md bg-inset px-1.5 text-[11.5px] font-medium text-ink-2 shadow-hairline tabular-nums">
{copy.count}
</span>
</div>
{chunks.map((chunk, i) => (
<div
key={chunk.title}
className="overflow-hidden rounded-card bg-card shadow-card"
style={{
animation: `fade-up 400ms cubic-bezier(0.23,1,0.32,1) ${i * 100}ms both`,
}}
>
<div className="primitive-card-bar flex items-center gap-2.5 border-b border-line">
<span className="flex min-w-0 items-center gap-1.5 text-[13px] font-medium text-ink">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M4 6h16M4 12h16M4 18h10" /></svg>
<span className="truncate">{chunk.title}</span>
</span>
<span className="ml-auto shrink-0 text-[12px] text-ink-3 tabular-nums">{chunk.chars}</span>
</div>
<p className="px-3 pt-2 pb-1 text-[12.5px] leading-relaxed text-ink-2">
{chunk.body}
</p>
<div className="px-3 pb-3">
<span
className="inline-flex h-6 items-center gap-1.5 rounded-full bg-inset px-2
text-[12px] font-medium text-ink-2 shadow-btn
transition-[opacity,transform,background-color] duration-300 hover:bg-hover"
style={{
opacity: chipsShown ? 1 : 0,
transform: chipsShown ? "scale(1)" : "scale(0.95)",
transitionTimingFunction: "cubic-bezier(0.23, 1, 0.32, 1)",
transitionDelay: `${i * 80}ms`,
}}
>
<span className={`flex size-3.5 items-center justify-center rounded-[4px] ${chunk.tone} text-[7px] font-bold text-white`}>
{chunk.badge}
</span>
{chunk.source}
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17L17 7M7 7h10v10" /></svg>
</span>
</div>
</div>
))}
</div>
);
}Installation
pnpm dlx shadcn@latest add @ward/context-cardsnpx shadcn@latest add @ward/context-cardsyarn dlx shadcn@latest add @ward/context-cardsbunx --bun shadcn@latest add @ward/context-cardsAdd the Ward registry to components.json once:
"registries": {
"@ward": "https://ward.so/r/{name}.json"
}Or install from the URL with no configuration: npx shadcn@latest add https://ward.so/r/context-cards.json
Add the Ward items it builds on.
npx shadcn@latest add @ward/foundationCopy each file from the Code tab into:
components/ward/ContextCards.tsx
Update the import paths to match your project.
Usage
import ContextCards from "@/components/ward/ContextCards";<ContextCards
labels={{ header: "Retrieved chunks", count: "1" }}
chunks={[
{
title: "Returns policy",
chars: "310 characters",
body: "Unopened items can be returned within 30 days of delivery.",
source: "Returns Policy.pdf",
badge: "PDF",
tone: "bg-red",
},
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | string | "Default" | Accepted for registry parity; the component has a single variant. |
chunks | ContextChunk[] | CHUNKS | Retrieved chunks to show, each with a title, character count, body text, source name, badge text and badge colour class. |
labels | Partial<ContextCardsLabels> | - | Overrides for the header text and the count shown beside it. |
className | string | - | Extra classes added to the outer wrapper. |
Ward · Version 1.1.0 · Registry manifest