Free SKILL.md scraped from GitHub. Clone the repo or copy the file directly into your Claude Code skills directory.
npx versuz@latest install tambo-ai-tambo-claude-skills-building-settings-uigit clone https://github.com/tambo-ai/tambo.gitcp tambo/SKILL.MD ~/.claude/skills/tambo-ai-tambo-claude-skills-building-settings-ui/SKILL.md---
name: building-settings-ui
description: >-
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.
metadata:
internal: true
---
# Building Settings UI
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.
## Architecture
Settings are split across two top-level tabs in the project layout:
- **Agent tab** (`/[projectId]/agent`) - How the AI agent behaves
- **Settings tab** (`/[projectId]/settings`) - Project infrastructure and access
Each tab renders a flat vertical stack of Card components. There is no sidebar navigation; each page is short enough to scroll naturally.
### Tab layout
```
Overview | Observability | Agent | Settings
```
- Layout file: `apps/web/app/(authed)/(dashboard)/[projectId]/layout.tsx`
- Agent page: `apps/web/app/(authed)/(dashboard)/[projectId]/agent/page.tsx`
- Settings page: `apps/web/app/(authed)/(dashboard)/[projectId]/settings/page.tsx`
## Gotchas
1. **Do not add a new top-level tab** without explicit team alignment. Current tabs (Overview, Observability, Agent, Settings) have been stable.
2. **`EditWithTamboButton` goes inside `CardTitle`**, not as a sibling of `CardHeader`. It must have a `description` prop explaining what the section configures.
3. **Invalidate the query before toasting** in `onSuccess`. Reversing the order can show a success toast while the UI still displays old data.
4. **Use `DeleteConfirmationDialog`**, never inline `AlertDialog` for destructive confirmations.
5. **Use `text-destructive` semantic color**, never `text-red-500`. Cancel/discard buttons are NOT destructive.
---
## Feature Placement
### Agent Tab Sections
| # | 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`
### Settings Tab Sections
| # | 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/`.
### Placement Decision Tree
1. **Configures AI agent behavior?** (model selection, prompts, tools, memory, context) -> **Agent tab**
2. **Configures project infrastructure?** (API keys, naming, deletion, billing, webhooks) -> **Settings tab**
3. **Configures who can access?** (auth, tokens, team members, permissions) -> **Settings tab**
4. **Monitoring or debugging view?** -> **Observability tab**
5. **High-level summary or status?** -> **Overview tab**
6. **None of the above?** -> Ask the user.
### Conditional and Dependent Settings
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:
```tsx
// 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:
```tsx
// 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:
```tsx
// provider-key-section.tsx passes selectedProvider to the parameter editor
<CustomLlmParametersEditor selectedProvider={selectedProvider} />
```
**Rules for new dependent settings:**
1. Never hide a section entirely based on another setting's state. Always render the card; use an Alert or disabled state to communicate the dependency.
2. The warning message must name the dependency and tell the user what to change (e.g., "Switch to a supported provider to enable skills").
3. Keep dependency checks in the section component, not the container. The container (`agent-settings.tsx`, `project-settings.tsx`) should pass data, not make visibility decisions.
4. If a setting depends on state from a different tab, pass it through the shared project query (`api.project.getProject`) rather than cross-tab state.
### Adding a New Section
1. **Create the component** in `project-details/` following the Card layout pattern below.
2. **Import and render** in the correct container (`agent-settings.tsx` or `project-settings.tsx`).
3. **Update the skeleton** in `settings-skeletons.tsx` (either `AgentPageSkeleton` or `SettingsPageSkeleton`).
---
## Component Patterns
### Card Layout
Every settings section uses `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent` from `@/components/ui/card`.
```tsx
<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>
```
### Toast Notifications
Every mutation shows a toast on both success and error. Import `useToast` from `@/hooks/use-toast`.
```tsx
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.
### Confirmation Dialogs
All destructive actions use `DeleteConfirmationDialog` from `@/components/dashboard-components/delete-confirmation-dialog`:
```tsx
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.
### Destructive Action Styling
```tsx
<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.
### Save Behavior
**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).
### Danger Zone Pattern
For irreversible destructive actions, use the Danger Zone card pattern:
```tsx
<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.