Browse all patterns
Model & Memory Preferences
A preferences screen that picks the model, reasoning level and tools, and manages persistent memory.
model-preferences.tsx199 lines4 componentsReact + Tailwind + shadcn/ui
npx shadcn@latest add @scrimui/model-preferencesAgent promptClaude Code · Cursor · any agent
Add the Model & Memory Preferences pattern from the Scrim UI registry to this project — a complete screen, not a single component.
Model & Memory Preferences — A preferences screen that picks the model, reasoning level and tools, and manages persistent memory.
## 1. Install
```bash
npx shadcn@latest add @scrimui/model-preferences
```
This writes the screen to `components/blocks/model-preferences.tsx` and pulls in the components it is built from, each landing at `components/ui/`. Everything is plain React + Tailwind with no runtime dependencies. The imports in the block already point at those paths, so it compiles as installed.
## 2. What it is made of
- `model-selector` — Model Selector
- `reasoning-level` — Reasoning Level
- `tool-toggle` — Tool Toggle
- `memory-list` — Memory List
Each is a separate file you can edit or replace on its own; the block is the arrangement, not a monolith.
## 3. Rules this layout depends on
Keep these when adapting the screen — they are the reasons it works, and they are easy to break while restyling:
- Group decisions that shape every answer — model, reasoning effort and tools — into one visible place, not buried in settings.
- Show the reasoning-level trade-off in words, not just a slider: tell the user what 'deep' will cost in latency.
- State what each tool is allowed to do next to its switch; a toggle without a permission boundary is a trap.
- Keep memory visible and removable next to the controls that depend on it — the user should see what the assistant holds.
- Persist these choices per conversation and per user; preferences that reset silently break trust.
## 4. Do not do these
- Spreading model, tools and memory across separate pages so the user cannot reason about them together.
- Letting the tool switches run without explaining the scope of each permission.
- Hiding saved memories behind a 'data' page — a user should not have to hunt to forget something.
- Adding a reasoning-level control that does not say how much slower 'deep' answers will be.
The demo content in the file — messages, file names, model names — is placeholder. Replace it with this project's real data and wire the handlers to real state rather than shipping the stubs.
Reference: https://scrimui.dev/patterns/model-preferencesInstalls the screen and every component it is built from, and carries the layout rules from this page so an agent does not restyle them away.
Live Preview
Model & Memory Preferences
How the assistant thinks, which tools it may use, and what it remembers.
Default model
Used for new conversations; you can still switch per message.
Reasoning effort
BalancedA good default for most tasks
Allowed tools
What the assistant may do on your behalf
Web search
Find current information online
Code execution
Run code to verify answers
File access
Read and edit attached files
Browser
Open pages from the conversation
Memory
Facts the assistant keeps across conversations
Builds AI chat interfaces in TypeScript with Tailwind and zero UI dependencies.
2d agoPrefers zinc-based palettes and compact, dependency-free components.
5d agoShipping a voice assistant pattern this week.
Today
Memories are included with each message so the assistant stays consistent. Remove anything you do not want stored.
Built from these components
A standalone model picker — compare models in a dropdown with badges for speed, context length and what each supports.
A control for how hard the model should think — light, balanced or deep, with the tradeoff between speed and depth shown.
Switches for the tools a model may use — web search, code execution, file access — with unmistakable on and off states.
The memory panel — every fact the assistant has saved about the user, with the ability to add one or make it forget.
Pattern code
This file composes the components above. Copy each component from its page, then this pattern file wires them together.
"use client";
import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import * as React from "react";
import { ModelSelector, type ModelOption } from "../../model-selector/model-selector";
import {
ReasoningLevel,
type ReasoningLevel as ReasoningLevelValue,
} from "../../reasoning-level/reasoning-level";
import { ToolToggle, type ToolSetting } from "../../tool-toggle/tool-toggle";
import { MemoryList, type MemoryItem } from "../../memory-list/memory-list";
/* ------------------------------------------------------------------ */
/* Data */
/* ------------------------------------------------------------------ */
const MODELS: ModelOption[] = [
{ id: "sonnet", name: "Claude Sonnet 5", hint: "Balanced speed and quality", badges: ["Default"], icon: <ClaudeMark /> },
{ id: "opus", name: "Claude Opus 5", hint: "Best for hard problems", badges: ["Deep thinking"], icon: <ClaudeMark /> },
{ id: "haiku", name: "Claude Haiku 4.5", hint: "Fastest responses", badges: ["Fast"], icon: <ClaudeMark /> },
];
function initTools(): ToolSetting[] {
return [
{
id: "search",
name: "Web search",
description: "Find current information online",
enabled: true,
icon: <SearchIcon />,
},
{
id: "code",
name: "Code execution",
description: "Run code to verify answers",
enabled: true,
icon: <CodeIcon />,
},
{
id: "files",
name: "File access",
description: "Read and edit attached files",
enabled: false,
icon: <FileIcon />,
},
{
id: "browser",
name: "Browser",
description: "Open pages from the conversation",
enabled: false,
icon: <GlobeIcon />,
},
];
}
const INITIAL_MEMORIES: MemoryItem[] = [
{
id: "m1",
text: "Builds AI chat interfaces in TypeScript with Tailwind and zero UI dependencies.",
updatedAt: "2d ago",
},
{ id: "m2", text: "Prefers zinc-based palettes and compact, dependency-free components.", updatedAt: "5d ago" },
{ id: "m3", text: "Shipping a voice assistant pattern this week.", updatedAt: "Today" },
];
/* ------------------------------------------------------------------ */
/* Icons */
/* ------------------------------------------------------------------ */
/**
* The provider's mark, inlined so this pattern stays dependency-free. Swap it
* for whichever providers your own model list uses.
*/
function ClaudeMark() {
return (
<svg viewBox="0 0 24 24" width="13" height="13" aria-hidden className="shrink-0 fill-[#d97757]">
<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" />
</svg>
);
}
function SearchIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="15" height="15">
<circle cx="11" cy="11" r="7" />
<path d="m21 21-4.3-4.3" />
</svg>
);
}
function CodeIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="15" height="15">
<path d="m8 7-5 5 5 5" />
<path d="m16 7 5 5-5 5" />
</svg>
);
}
function FileIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="15" height="15">
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2Z" />
<path d="M14 2v6h6" />
</svg>
);
}
function GlobeIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="15" height="15">
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 0 20 15.3 15.3 0 0 1 0-20Z" />
</svg>
);
}
/* ------------------------------------------------------------------ */
/* ModelPreferencesPattern */
/* ------------------------------------------------------------------ */
export function ModelPreferencesPattern() {
const [model, setModel] = React.useState("sonnet");
const [level, setLevel] = React.useState<ReasoningLevelValue>("balanced");
const [tools, setTools] = React.useState<ToolSetting[]>(initTools);
const [memories, setMemories] = React.useState<MemoryItem[]>(INITIAL_MEMORIES);
const idRef = React.useRef(4);
function toggleTool(id: string, enabled: boolean) {
setTools((ts) => ts.map((t) => (t.id === id ? { ...t, enabled } : t)));
}
function addMemory(text: string) {
setMemories((m) => [...m, { id: `m${idRef.current++}`, text, updatedAt: "Just now" }]);
}
function forgetMemory(id: string) {
setMemories((m) => m.filter((item) => item.id !== id));
}
return (
<Card className="gap-0 py-0 flex h-[560px] flex-col overflow-hidden rounded-xl border border-border bg-card">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-5 py-3.5">
<div>
<p className="text-sm font-semibold text-foreground">
Model & Memory Preferences
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
How the assistant thinks, which tools it may use, and what it remembers.
</p>
</div>
<Badge variant="secondary" className="inline-flex items-center gap-1.5 rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-medium text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400">
<span className="h-1.5 w-1.5 rounded-full bg-current" />
Saved
</Badge>
</div>
<div className="flex min-h-0 flex-1">
{/* Settings */}
<div className="flex-1 space-y-6 overflow-y-auto p-5">
<section className="space-y-2">
<div>
<p className="text-sm font-medium text-foreground">Default model</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Used for new conversations; you can still switch per message.
</p>
</div>
<ModelSelector options={MODELS} value={model} onSelect={setModel} />
</section>
<ReasoningLevel value={level} onChange={setLevel} />
<ToolToggle
tools={tools}
onToggle={toggleTool}
title="Allowed tools"
description="What the assistant may do on your behalf"
/>
</div>
{/* Memory */}
<div className="hidden w-80 shrink-0 flex-col overflow-y-auto border-l border-border p-5 md:flex">
<MemoryList
items={memories}
onAdd={addMemory}
onForget={forgetMemory}
description="Facts the assistant keeps across conversations"
/>
<p className="mt-3 text-xs leading-5 text-muted-foreground">
Memories are included with each message so the assistant stays consistent. Remove
anything you do not want stored.
</p>
</div>
</div>
</Card>
);
}
When to use it
- Group decisions that shape every answer — model, reasoning effort and tools — into one visible place, not buried in settings.
- Show the reasoning-level trade-off in words, not just a slider: tell the user what 'deep' will cost in latency.
- State what each tool is allowed to do next to its switch; a toggle without a permission boundary is a trap.
- Keep memory visible and removable next to the controls that depend on it — the user should see what the assistant holds.
- Persist these choices per conversation and per user; preferences that reset silently break trust.
What breaks in production
- Spreading model, tools and memory across separate pages so the user cannot reason about them together.
- Letting the tool switches run without explaining the scope of each permission.
- Hiding saved memories behind a 'data' page — a user should not have to hunt to forget something.
- Adding a reasoning-level control that does not say how much slower 'deep' answers will be.
More Patterns
The canonical chat interface — sidebar, streaming messages, prompt input with model selector, and sources.
AI Research AssistantA research flow that shows search tool calls, reasoning, sources and a cited final answer.
AI Coding AgentA coding run with agent status, tool calls, diffs and a human-in-the-loop approval gate.
AI Voice AssistantA voice-first conversation — live waveform states, a recording input, a spoken transcript and a typed fallback.
Artifact WorkspaceChat on the left, generated output on the right — artifacts open from the answer, stream, version, and fail without breaking the conversation.
Document Q&A WorkspaceAsk your own documents — upload and parse, cited answers with inspectable passages, an honest not-found state, and a visible context budget.
Structured Extraction & ReviewUpload a document, watch fields fill in, then review the flagged ones — per-field confidence, corrections that keep the original, export earned.
Image Generation StudioA one-screen generation studio — prompt composer and model picker beside a feed of queued, staged, blocked and ready image results with variants.
Multi-agent Ops ConsoleWatch a fleet of agents at once — parallel statuses, an inspectable handoff, a waiting approval, a failed child run, and per-run plus fleet cost.
Customer Support CopilotGrounded reply drafts with citations, honest low-confidence answers, inline corrections, an approval gate on refunds, and a rating row on every draft.
Generative UI DashboardThe model assembles a dashboard from a controlled widget registry — streamed props, an unsupported-request fallback, and widget clicks that re-enter the chat.