/creating-styled-wrappers
Creates styled wrapper components that compose headless/base compound components. Use when refactoring styled components to use base primitives, implementing opinionated design systems on top of headless components, or when the user mentions "use base components", "compose
$ npx -y skills add tambo-ai/tambo --skill creating-styled-wrappers --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/creating-styled-wrappers
Context preview
The summary Claude sees to decide when to auto-load this skill.
Creates styled wrapper components that compose headless/base compound components. Use when refactoring styled components to use base primitives, implementing opinionated design systems on top of headless components, or when the user mentions "use base components", "compose
SKILL.md
creating-styled-wrappers.SKILL.mdname: creating-styled-wrappers
description: Creates styled wrapper components that compose headless/base compound components. Use when refactoring styled components to use base primitives, implementing opinionated design systems on top of headless components, or when the user mentions "use base components", "compose primitives", "styled wrapper", or "refactor to use base".
metadata:
internal: true
Styling Compound Wrappers
Create styled wrapper components that compose headless base compound components. This skill complements `building-compound-components` (which builds the base primitives) by focusing on **how to properly consume and wrap them** with styling and additional behavior.
**Real-world example**: See [references/real-world-example.md](references/real-world-example.md) for a complete before/after MessageInput refactoring.
Core Principle: Compose, Don't Duplicate
Styled wrappers should **compose** base components, not **re-implement** their logic.
// WRONG - re-implementing what base already does
const StyledInput = ({ children, className }) => {
const { value, setValue, submit } = useTamboThreadInput(); // Duplicated!
const [isDragging, setIsDragging] = useState(false); // Duplicated!
const handleDrop = useCallback(/* ... */); // Duplicated!
return (
<form onDrop={handleDrop} className={className}>
{children}
</form>
);
};
// CORRECT - compose the base component
const StyledInput = ({ children, className, variant }) => {
return (
<BaseInput.Root className={cn(inputVariants({ variant }), className)}>
<BaseInput.Content className="rounded-xl data-[dragging]:border-dashed">
{children}
</BaseInput.Content>
</BaseInput.Root>
);
};Refactoring Workflow
Copy this checklist and track progress:
Styled Wrapper Refactoring:
- [ ] Step 1: Identify duplicated logic
- [ ] Step 2: Import base components
- [ ] Step 3: Wrap with Base Root
- [ ] Step 4: Apply state-based styling and behavior
- [ ] Step 5: Wrap sub-components with styling
- [ ] Step 6: Final verification
Step 1: Identify Duplicated Logic
Look for patterns that indicate logic should come from base:
- SDK hooks (`useTamboThread`, `useTamboThreadInput`, etc.)
- Context creation (`React.createContext`)
- State management that mirrors base component state
- Event handlers (drag, submit, etc.) that base components handle
Step 2: Import Base Components
import { MessageInput as MessageInputBase } from "@tambo-ai/react-ui-base/message-input";Step 3: Wrap with Base Root
Replace custom context/state management with the base Root:
// Before
const MessageInput = ({ children, variant }) => {
return (
<MessageInputInternal variant={variant}>{children}</MessageInputInternal>
);
};
// After
const MessageInput = ({ children, variant, className }) => {
return (
<MessageInputBase.Root className={cn(variants({ variant }), className)}>
{children}
</MessageInputBase.Root>
);
};Step 4: Apply State-Based Styling and Behavior
State access follows a hierarchy — use the simplest option that works:
1. **Data attributes** (preferred for styling) — base components expose `data-*` attributes 2. **Render props** (for behavior changes) — use when rendering different components 3. **Context hooks** (for sub-components) — OK for styled sub-components needing deep context access
// BEST - data-* classes for styling, render props only for behavior
// Note: use `data-[dragging]:*` syntax (v3-compatible), not `data-dragging:*` (v4 only)
const StyledContent = ({ children }) => (
<BaseComponent.Content
className={cn(
"group rounded-xl border",
"data-[dragging]:border-dashed data-[dragging]:border-emerald-400",
)}
>
{({ elicitation, resolveElicitation }) => (
<>
{/* Drop overlay uses group-data-* for styling */}
<div className="hidden group-data-[dragging]:flex absolute inset-0 bg-emerald-50/90">
<p>Drop files here</p>
</div>
{elicitation ? (
<ElicitationUI
request={elicitation}
onResponse={resolveElicitation}
/>
) : (
children
)}
</>
)}
</BaseComponent.Content>
);
// OK - styled sub-components can use context hook for deep access
const StyledTextarea = ({ placeholder }) => {
const { value, setValue, handleSubmit, editorRef } = useMessageInputContext();
return (
<CustomEditor
ref={editorRef}
value={value}
onChange={setValue}
onSubmit={handleSubmit}
placeholder={placeholder}
/>
);
};**When to use context hooks vs render props:**
- Render props: when the parent wrapper needs state for behavior changes
- Context hooks: when a styled sub-component needs values not exposed via render props
Step 5: Wrap Sub-Components
// Submit button
const SubmitButton = ({ className, children }) => (
<BaseComponent.SubmitButton className={cn("w-10 h-10 rounded-lg", className)}>
{({ showCancelButton }) =>
children ?? (showCancelButton ? <Square /> : <ArrowUp />)
}
</BaseComponent.SubmitButton>
);
// Error
const Error = ({ className }) => (
<BaseComponent.Error className={cn("text-sm text-destructive", className)} />
);
// Staged images - base pre-computes props array, just iterate
const StagedImages = ({ className }) => (
<BaseComponent.StagedImages className={cn("flex gap-2", className)}>
{({ images }) =>
images.map((imageProps) => (
<ImageBadge key={imageProps.image.id} {...imageProps} />
))
}
</BaseComponent.StagedImages>
);Step 6: Final Verification
Final Checks:
- [ ] No duplicate context creation
- [ ] No duplicate SDK hooks in root wrappers
- [ ] No duplicate state management or event handlers
- [ ] Base namespace imported and `Base.Root` used as wrapper
- [ ] `data-*` classes used for styling (with `group-data-*`
Read more
name: creating-styled-wrappers description: Creates styled wrapper components that compose headless/base compound components. Use when refactoring styled components to use base primitives, implementing opinionated design systems on top of headless components, or when the user mentions "use base components", "compose primitives", "styled wrapper", or "refactor to use base". metadata: internal: true
Styling Compound Wrappers
Create styled wrapper components that compose headless base compound components. This skill complements `building-compound-components` (which builds the base primitives) by focusing on **how to properly consume and wrap them** with styling and additional behavior.
**Real-world example**: See [references/real-world-example.md](references/real-world-example.md) for a complete before/after MessageInput refactoring.
Core Principle: Compose, Don't Duplicate
Styled wrappers should **compose** base components, not **re-implement** their logic.
// WRONG - re-implementing what base already does
const StyledInput = ({ children, className }) => {
const { value, setValue, submit } = useTamboThreadInput(); // Duplicated!
const [isDragging, setIsDragging] = useState(false); // Duplicated!
const handleDrop = useCallback(/* ... */); // Duplicated!
return (
<form onDrop={handleDrop} className={className}>
{children}
</form>
);
};
// CORRECT - compose the base component
const StyledInput = ({ children, className, variant }) => {
return (
<BaseInput.Root className={cn(inputVariants({ variant }), className)}>
<BaseInput.Content className="rounded-xl data-[dragging]:border-dashed">
{children}
</BaseInput.Content>
</BaseInput.Root>
);
};Refactoring Workflow
Copy this checklist and track progress:
Styled Wrapper Refactoring: - [ ] Step 1: Identify duplicated logic - [ ] Step 2: Import base components - [ ] Step 3: Wrap with Base Root - [ ] Step 4: Apply state-based styling and behavior - [ ] Step 5: Wrap sub-components with styling - [ ] Step 6: Final verification
Step 1: Identify Duplicated Logic
Look for patterns that indicate logic should come from base:
- SDK hooks (`useTamboThread`, `useTamboThreadInput`, etc.)
- Context creation (`React.createContext`)
- State management that mirrors base component state
- Event handlers (drag, submit, etc.) that base components handle
Step 2: Import Base Components
import { MessageInput as MessageInputBase } from "@tambo-ai/react-ui-base/message-input";Step 3: Wrap with Base Root
Replace custom context/state management with the base Root:
// Before
const MessageInput = ({ children, variant }) => {
return (
<MessageInputInternal variant={variant}>{children}</MessageInputInternal>
);
};
// After
const MessageInput = ({ children, variant, className }) => {
return (
<MessageInputBase.Root className={cn(variants({ variant }), className)}>
{children}
</MessageInputBase.Root>
);
};Step 4: Apply State-Based Styling and Behavior
State access follows a hierarchy — use the simplest option that works:
1. **Data attributes** (preferred for styling) — base components expose `data-*` attributes 2. **Render props** (for behavior changes) — use when rendering different components 3. **Context hooks** (for sub-components) — OK for styled sub-components needing deep context access
// BEST - data-* classes for styling, render props only for behavior
// Note: use `data-[dragging]:*` syntax (v3-compatible), not `data-dragging:*` (v4 only)
const StyledContent = ({ children }) => (
<BaseComponent.Content
className={cn(
"group rounded-xl border",
"data-[dragging]:border-dashed data-[dragging]:border-emerald-400",
)}
>
{({ elicitation, resolveElicitation }) => (
<>
{/* Drop overlay uses group-data-* for styling */}
<div className="hidden group-data-[dragging]:flex absolute inset-0 bg-emerald-50/90">
<p>Drop files here</p>
</div>
{elicitation ? (
<ElicitationUI
request={elicitation}
onResponse={resolveElicitation}
/>
) : (
children
)}
</>
)}
</BaseComponent.Content>
);
// OK - styled sub-components can use context hook for deep access
const StyledTextarea = ({ placeholder }) => {
const { value, setValue, handleSubmit, editorRef } = useMessageInputContext();
return (
<CustomEditor
ref={editorRef}
value={value}
onChange={setValue}
onSubmit={handleSubmit}
placeholder={placeholder}
/>
);
};**When to use context hooks vs render props:**
- Render props: when the parent wrapper needs state for behavior changes
- Context hooks: when a styled sub-component needs values not exposed via render props
Step 5: Wrap Sub-Components
// Submit button
const SubmitButton = ({ className, children }) => (
<BaseComponent.SubmitButton className={cn("w-10 h-10 rounded-lg", className)}>
{({ showCancelButton }) =>
children ?? (showCancelButton ? <Square /> : <ArrowUp />)
}
</BaseComponent.SubmitButton>
);
// Error
const Error = ({ className }) => (
<BaseComponent.Error className={cn("text-sm text-destructive", className)} />
);
// Staged images - base pre-computes props array, just iterate
const StagedImages = ({ className }) => (
<BaseComponent.StagedImages className={cn("flex gap-2", className)}>
{({ images }) =>
images.map((imageProps) => (
<ImageBadge key={imageProps.image.id} {...imageProps} />
))
}
</BaseComponent.StagedImages>
);Step 6: Final Verification
Final Checks: - [ ] No duplicate context creation - [ ] No duplicate SDK hooks in root wrappers - [ ] No duplicate state management or event handlers - [ ] Base namespace imported and `Base.Root` used as wrapper - [ ] `data-*` classes used for styling (with `group-data-*`
Repo: tambo-ai/tambo
Other skills on tambo.
- /ai-sdk-model-manager
Manages AI SDK model configurations - updates packages, identifies missing models, adds new models with research, and updates documentation
Open skill - /api-resource-lifecycle
Guides CRUD operations for API resources with cascading dependencies, descriptive validation, and orphan prevention. Use when adding delete/remove operations, creating validation logic, building resources that depend on other resources, or when the user mentions "cascade
Open skill - /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
Open skill - /compound-components
Creates unstyled compound components that separate business logic from styles. Use when building headless UI primitives, creating component libraries, implementing Radix-style namespaced components, or when the user mentions "compound components", "headless", "unstyled",
Open skill - /validating-accessibility
Use this skill when creating, modifying, or reviewing any .tsx component in apps/web, even if the user doesn't mention "accessibility." Covers semantic HTML, aria labels, navigation landmarks, forms, dialogs, and keyboard navigation. Trigger on: adding buttons, links, toggles,
Open skill - /building-with-tambo
Integrates Tambo into existing React apps — detects tech stack, installs @tambo-ai/react, wires TamboProvider, registers components with Zod schemas, and sets up tools/context. Use when adding AI-powered generative UI to an existing codebase. Triggers on "add Tambo", "integrate
Open skill

