Browse all components
Memory List UI
The memory panel — every fact the assistant has saved about the user, with the ability to add one or make it forget.
Preview
Memory
Facts the assistant keeps across conversations
Prefers TypeScript with strict mode on
Works in Shenzhen, UTC+8
Ships design-system work in Tailwind, not CSS modules
Presets
Props
Also takes onAdd, onForget.
Agent promptClaude Code · Cursor · any agent
Add the Memory List UI component from the Scrim UI registry to this project and use it in the configuration described below.
Memory List UI — The memory panel — every fact the assistant has saved about the user, with the ability to add one or make it forget.
## 1. Install
```bash
npx shadcn@latest add @scrimui/memory-list
```
This writes a single file to `components/ui/memory-list.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/memory-list to the same path by hand; nothing in the file depends on shadcn.
## 2. Use it
```tsx
import { MemoryList } from "@/components/ui/memory-list";
const ITEMS = [
{ id: "m1", text: "Prefers TypeScript with strict mode on" },
{ id: "m2", text: "Works in Shenzhen, UTC+8" },
{ id: "m3", text: "Ships design-system work in Tailwind, not CSS modules" },
];
<MemoryList
title="Memory"
description="Facts the assistant keeps across conversations"
emptyText="Nothing saved yet."
items={ITEMS}
onAdd={() => {}}
onForget={() => {}}
/>
```
## 3. The configuration that matters
This is the component's "Default" state — The assistant's working memory — add a fact, forget an old one.
Every prop is at its default value. Keep the call site minimal — do not write out default values.
## 4. Wire the handlers
`onAdd`, `onForget` are stubs in the snippet above (`() => {}`). Connect them to this project's own state and data layer instead of shipping the empty functions.
Reference: https://scrimui.dev/components/memory-listFollows 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/memory-listComponent source
React + Tailwind component using shadcn/ui (badge, button, card, input). The install command adds these dependencies automatically.
When copying source manually, install the required primitives first:
npx shadcn@latest add badge button card input"use client";
import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import * as React from "react";
/* ------------------------------------------------------------------ */
/* Types */
/* ------------------------------------------------------------------ */
export type MemoryItem = {
id: string;
text: string;
updatedAt?: string;
};
export type MemoryListProps = {
items: MemoryItem[];
title?: string;
description?: string;
addPlaceholder?: string;
emptyText?: string;
onAdd?: (text: string) => void;
onForget?: (id: string) => void;
className?: string;
};
/* ------------------------------------------------------------------ */
/* Icons */
/* ------------------------------------------------------------------ */
function SparkleIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
width="14"
height="14"
{...props}
>
<path d="M12 3l1.9 5.8a2 2 0 0 0 1.3 1.3L21 12l-5.8 1.9a2 2 0 0 0-1.3 1.3L12 21l-1.9-5.8a2 2 0 0 0-1.3-1.3L3 12l5.8-1.9a2 2 0 0 0 1.3-1.3z" />
</svg>
);
}
function PlusIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
width="12"
height="12"
{...props}
>
<path d="M12 5v14M5 12h14" />
</svg>
);
}
function XIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
width="12"
height="12"
{...props}
>
<path d="M18 6 6 18M6 6l12 12" />
</svg>
);
}
/* ------------------------------------------------------------------ */
/* MemoryList */
/* ------------------------------------------------------------------ */
export function MemoryList({
items,
title = "Memory",
description = "Things the assistant remembers about you",
addPlaceholder = "Add a memory…",
emptyText = "No memories yet.",
onAdd,
onForget,
className = "",
}: MemoryListProps) {
const [draft, setDraft] = React.useState("");
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const text = draft.trim();
if (!text || !onAdd) return;
onAdd(text);
setDraft("");
}
return (
<Card className={`gap-0 py-0 rounded-xl border border-border bg-card ${className}`}>
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-4 py-3">
<div>
<p className="text-sm font-medium text-foreground">{title}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{description}</p>
</div>
<Badge variant="secondary" className="rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">
{items.length} {items.length === 1 ? "item" : "items"}
</Badge>
</div>
{/* Items */}
{items.length === 0 ? (
<p className="px-4 py-8 text-center text-xs text-muted-foreground">{emptyText}</p>
) : (
<ul className="max-h-56 divide-y divide-border overflow-y-auto">
{items.map((item) => (
<li key={item.id} className="group flex items-center gap-3 px-4 py-2.5">
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
<SparkleIcon />
</span>
<p className="min-w-0 flex-1 text-sm leading-5 text-foreground">
{item.text}
</p>
{item.updatedAt && (
<span className="shrink-0 text-xs text-muted-foreground">{item.updatedAt}</span>
)}
{onForget && (
<Button variant="ghost" size="icon-sm"
type="button"
aria-label={`Forget: ${item.text}`}
onClick={() => onForget(item.id)}
className="min-h-6 min-w-6 shrink-0 rounded-md p-1 text-muted-foreground opacity-100 transition-opacity hover:bg-muted hover:text-muted-foreground group-hover:opacity-100"
>
<XIcon />
</Button>
)}
</li>
))}
</ul>
)}
{/* Add memory */}
{onAdd && (
<form
onSubmit={handleSubmit}
className="flex items-center gap-2 border-t border-border px-4 py-3"
>
<Input
value={draft}
onChange={(e) => setDraft(e.target.value)}
placeholder={addPlaceholder}
aria-label="Add a memory"
className="min-w-0 flex-1 bg-transparent text-base sm:text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
/>
<Button variant="default" size="sm"
type="submit"
disabled={!draft.trim()}
className="inline-flex h-8 shrink-0 items-center gap-1 rounded-md bg-primary px-2.5 text-xs font-medium text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40"
>
<PlusIcon />
Add
</Button>
</form>
)}
</Card>
);
}
When to use it
- Show memory somewhere discoverable — a sidebar panel or settings page, never hidden.
- Make every memory forgettable in one click; forgetting should be instant and permanent.
- Let the user add memories directly, not only what the assistant happens to save.
- Label when each memory was captured so stale facts are easy to spot.
What breaks in production
- Storing sensitive data such as passwords or secrets without warning the user.
- No bulk management — an un-sortable list becomes a liability as it grows.
- Quietly remembering things the user never agreed to save.
Related Components
The prompt an assistant shows before saving something about you — the fact it noticed, and your choice to keep or dismiss it.
A small inline chip confirming a fact was saved to memory — quiet enough to ignore, clear enough to undo.
The 'Memory updated' receipt — shows the exact fact that was saved, with one-tap Undo and a quiet link to manage memories.
The message input at the heart of an AI chat app — file attachments, model picker, tool toggles, voice, and a send button that turns into stop.