Skip to main content

building-settings-ui

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.

Source facts

Repository
tambo-ai/tambo
Last source activity
April 8, 2026 at 18:32
Detected SKILL.md language
English
Stars
11,181
Forks
570

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
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.
View on GitHub