Use this skill when adding or modifying settings UI in Tambo Cloud. Covers where a new settings section belongs (Agent tab vs Settings tab), and the component patterns used across both pages (card layout, toasts, confirmation dialogs, destructive styling, save behavior conventions). Triggers on "add a new settings section", "where should X go?", "settings UI", "settings page", "agent page", or any work touching apps/web/components/dashboard-components/project-details/, project-settings.tsx, or agent-settings.tsx. Not for full-stack feature building (DB, tRPC, tests); those patterns will get their own skills.
Guide for placing and styling settings sections in the Tambo Cloud dashboard. Covers two concerns: where a feature belongs (which tab/page), and how to build the UI component to match existing patterns.
Settings are split across two top-level tabs in the project layout:
/[projectId]/agent) - How the AI agent behaves/[projectId]/settings) - Project infrastructure and accessEach tab renders a flat vertical stack of Card components. There is no sidebar navigation; each page is short enough to scroll naturally.
Overview | Observability | Agent | Settings
apps/web/app/(authed)/(dashboard)/[projectId]/layout.tsxapps/web/app/(authed)/(dashboard)/[projectId]/agent/page.tsxapps/web/app/(authed)/(dashboard)/[projectId]/settings/page.tsxEditWithTamboButton goes inside CardTitle, not as a sibling of CardHeader. It must have a description prop explaining what the section configures.onSuccess. Reversing the order can show a success toast while the UI still displays old data.DeleteConfirmationDialog, never inline AlertDialog for destructive confirmations.text-destructive semantic color, never text-red-500. Cancel/discard buttons are NOT destructive.| # | Section | What it configures | Component |
|---|---|---|---|
| 1 | Model | Provider + model selection, API key, custom params | provider-key-section.tsx |
| 2 | Custom Instructions | System prompt, prompt override toggle | custom-instructions-editor.tsx |
| 3 | Skills | Skill definitions and imports | skills-section.tsx |
| 4 | Tool Call Limit | Max tool calls per response | tool-call-limit-editor.tsx |
| 5 | MCP | MCP server URLs + headers | available-mcp-servers.tsx |
Container: apps/web/components/dashboard-components/agent-settings.tsx
| # | Section | What it configures | Component |
|---|---|---|---|
| 1 | Name | Project display name | project-name-section.tsx |
| 2 | API Keys | API key list + create | api-key-list.tsx |
| 3 | Authentication | OAuth mode, token requirements | oauth-settings.tsx |
| 4 | Danger Zone | Project deletion | danger-zone-section.tsx |
Container: apps/web/components/dashboard-components/project-settings.tsx
All section components live in apps/web/components/dashboard-components/project-details/.
Some settings only apply when another setting is in a specific state. Follow these patterns:
Show but warn (soft dependency). The section renders normally but displays an Alert when the dependency isn't met. The user can still see and configure the setting. Use this when the feature exists but won't work at runtime.
Example: Skills section shows a provider compatibility notice when the selected provider doesn't support skills:
// skills-section.tsx
const isProviderSupported = SKILLS_SUPPORTED_PROVIDERS.has(
defaultLlmProviderName,
);
// Renders full skills UI + warning Alert if !isProviderSupported
Conditionally pass props (data dependency). The parent reads one setting and passes it as a prop so the child can adapt its behavior. Use this when the child's content or options change based on the parent's state.
Example: MCP servers section receives providerType to toggle agent-mode-specific UI:
// agent-settings.tsx
<AvailableMcpServers providerType={projectData?.providerType} />;
// available-mcp-servers.tsx
const isAgentMode = providerType === AiProviderType.AGENT;
Example: Custom LLM parameters change available suggestions based on provider and model:
// provider-key-section.tsx passes selectedProvider to the parameter editor
<CustomLlmParametersEditor selectedProvider={selectedProvider} />
Rules for new dependent settings:
agent-settings.tsx, project-settings.tsx) should pass data, not make visibility decisions.api.project.getProject) rather than cross-tab state.project-details/ following the Card layout pattern below.agent-settings.tsx or project-settings.tsx).settings-skeletons.tsx (either AgentPageSkeleton or SettingsPageSkeleton).Every settings section uses Card, CardHeader, CardTitle, CardDescription, CardContent from @/components/ui/card.
<Card>
<CardHeader>
<CardTitle className="text-lg font-semibold">
Section Name
<EditWithTamboButton description="Configure section settings..." />
</CardTitle>
<CardDescription className="text-sm font-sans text-foreground">
Description of what this section does.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">{/* Content */}</CardContent>
</Card>
Every mutation shows a toast on both success and error. Import useToast from @/hooks/use-toast.
const mutation = api.someRoute.someMutation.useMutation({
onSuccess: async () => {
await utils.someRoute.someQuery.invalidate();
toast({ title: "Success", description: "Setting updated successfully" });
},
onError: () => {
toast({
title: "Error",
description: "Failed to update setting",
variant: "destructive",
});
},
});
Never skip the error toast.
All destructive actions use DeleteConfirmationDialog from @/components/dashboard-components/delete-confirmation-dialog:
const [alertState, setAlertState] = useState<{
show: boolean;
title: string;
description: string;
data?: { id: string };
}>({ show: false, title: "", description: "" });
<DeleteConfirmationDialog
mode="single"
alertState={alertState}
setAlertState={setAlertState}
onConfirm={handleConfirmDelete}
/>;
Title includes the item name (Delete "${name}"?). Description warns the action cannot be undone.
<Button
variant="ghost"
size="icon"
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash2 className="h-4 w-4" />
</Button>
Use Trash2 from lucide-react. Use hover:bg-destructive/10 for ghost variant hover.
Toggles: Immediate save. onCheckedChange fires the mutation directly. Include aria-label with state context.
Form fields: Edit/Save/Cancel. Track isEditing, savedValue, and displayValue state. Cancel reverts to savedValue. Save button disabled during mutation, shows "Saving...". autoFocus on first input when entering edit mode.
Reference implementations: custom-instructions-editor.tsx (edit/save/cancel), tool-call-limit-editor.tsx (simpler form), project-name-section.tsx (basic edit/save/cancel).
For irreversible destructive actions, use the Danger Zone card pattern:
<Card className="border-destructive/50">
<CardHeader>
<CardTitle>Danger Zone</CardTitle>
<CardDescription>Warning about permanence.</CardDescription>
</CardHeader>
<CardContent>
<Button
variant="ghost"
className="text-destructive hover:text-destructive hover:bg-destructive/10"
aria-label="Delete this project"
>
Delete this project
</Button>
</CardContent>
</Card>
The DeleteConfirmationDialog should be owned by the parent component that handles the mutation and post-delete navigation.