Browse all components
Tool Toggle UI
Switches for the tools a model may use — web search, code execution, file access — with unmistakable on and off states.
Preview
Tools
What the assistant is allowed to use
Web search
Find current information online
Code execution
Run code to verify answers
File access
Read and edit attached files
Presets
Props
Also takes onToggle.
Agent promptClaude Code · Cursor · any agent
Add the Tool Toggle UI component from the Scrim UI registry to this project and use it in the configuration described below.
Tool Toggle UI — Switches for the tools a model may use — web search, code execution, file access — with unmistakable on and off states.
## 1. Install
```bash
npx shadcn@latest add @scrimui/tool-toggle
```
This writes a single file to `components/ui/tool-toggle.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/tool-toggle to the same path by hand; nothing in the file depends on shadcn.
## 2. Use it
```tsx
import { ToolToggle } from "@/components/ui/tool-toggle";
const TOOLS = [
{ id: "t1", name: "Web search", description: "Find current information online", enabled: true },
{ id: "t2", name: "Code execution", description: "Run code to verify answers", enabled: true },
{ id: "t3", name: "File access", description: "Read and edit attached files", enabled: false },
];
<ToolToggle
title="Tools"
description="What the assistant is allowed to use"
tools={TOOLS}
onToggle={() => {}}
/>
```
## 3. The configuration that matters
This is the component's "Default" state — Live switches for the assistant's tools — flip one and the state updates.
Every prop is at its default value. Keep the call site minimal — do not write out default values.
## 4. Wire the handlers
`onToggle` is a stub in the snippet above (`() => {}`). Connect it to this project's own state and data layer instead of shipping the empty function.
Reference: https://scrimui.dev/components/tool-toggleFollows 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/tool-toggleComponent source
React + Tailwind component using shadcn/ui (button, card). The install command adds these dependencies automatically.
When copying source manually, install the required primitives first:
npx shadcn@latest add button card"use client";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import * as React from "react";
/* ------------------------------------------------------------------ */
/* Types */
/* ------------------------------------------------------------------ */
export type ToolSetting = {
id: string;
name: string;
description: string;
enabled: boolean;
disabled?: boolean;
icon?: React.ReactNode;
};
export type ToolToggleProps = {
tools: ToolSetting[];
title?: string;
description?: string;
onToggle?: (id: string, enabled: boolean) => void;
className?: string;
};
/* ------------------------------------------------------------------ */
/* ToolToggle */
/* ------------------------------------------------------------------ */
export function ToolToggle({
tools,
title = "Tools",
description = "What the assistant is allowed to use",
onToggle,
className = "",
}: ToolToggleProps) {
/* Prefix for the per-row label/description ids the switches point at. */
const uid = React.useId();
return (
<Card className={`gap-0 py-0 rounded-xl border border-border bg-card ${className}`}>
<div className="border-b border-border px-4 py-3">
<p className="text-sm font-medium text-foreground">{title}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{description}</p>
</div>
<ul className="divide-y divide-border">
{tools.map((tool) => (
<li key={tool.id} className="flex items-center gap-3 px-4 py-3">
{tool.icon && (
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
{tool.icon}
</span>
)}
<div className="min-w-0 flex-1">
<p
id={`${uid}-${tool.id}-name`}
className="text-sm font-medium text-foreground"
>
{tool.name}
</p>
<p
id={`${uid}-${tool.id}-desc`}
className="mt-0.5 text-xs text-muted-foreground"
>
{tool.description}
</p>
</div>
{/* The switch is a bare coloured pill, so without these it has no
accessible name at all — a screen reader announces "switch, on"
with no clue which tool it governs. Pointing at the visible
label rather than duplicating the string in an aria-label keeps
the two from drifting apart. */}
<Button variant="ghost" size="sm"
type="button"
role="switch"
aria-checked={tool.enabled}
aria-disabled={tool.disabled}
aria-labelledby={`${uid}-${tool.id}-name`}
aria-describedby={`${uid}-${tool.id}-desc`}
onClick={() => !tool.disabled && onToggle?.(tool.id, !tool.enabled)}
className={`relative h-5 w-9 shrink-0 rounded-full transition-colors ${
tool.enabled ? "bg-primary" : "bg-muted"
} ${tool.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`}
>
<span
className={`absolute left-0.5 top-0.5 h-4 w-4 rounded-full bg-white shadow-sm transition-transform ${
tool.enabled ? "translate-x-4" : ""
}`}
/>
</Button>
</li>
))}
</ul>
</Card>
);
}
When to use it
- Describe each tool's permission in plain language — 'can read files' beats a tool name.
- Default tools on when they are safe and clearly benefit the user's goal.
- Show locked tools as disabled with a reason, not hidden — it motivates the upgrade.
- Announce what a toggle change means in the next response (web search on → answers cite sources).
What breaks in production
- Generic tool names with no idea what the tool can actually access.
- Toggles with no visible consequence — the assistant ignores them or never says it used a tool.
- Offering an 'allow everything' master switch that nullifies granular controls.
Related 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.
Live spend per message and per conversation — cached input priced apart from fresh, reasoning tokens shown without double-counting, and a visible ~ when a provider did not report enough to be sure.
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.