Styling using the Camp Design System
Loading...
Overview
styled-components
Styled-components is a popular library for React that enables developers to write “CSS-in-JS.” This is our chosen solution for theming and light/dark mode support in our system.
How to Set Up
If you are using any of the following, you will need to set up a ThemeProvider. Note that this is often already in place.
- Any
@camp/ai-*components (ActiveIntelligence components) - Themed
@camp/*components (check individual component docs) - Custom styled-components that use theme tokens
Component Coverage
| Component Type | ThemeProvider Required | Default Themed Behavior |
|---|---|---|
@camp/ai-* | ✅ Always | themed={true} (dark mode enabled) |
@camp/* (themed) | ✅ Required | themed={false} (light mode only) |
@camp/* (standard) | ❌ Not needed | No theming support |
Note: If a @camp/* component requires a ThemeProvider, it will be noted in the component’s documentation.
Setup
Wrap Your App with ThemeProvider
Wrap your application (or the section where you’ll use themed components) with the ThemeProvider:
// Using the shared provider from AC Frontend
import { ThemeProvider } from '@ac/shared/data-access/provider';
import { Tabs } from '@camp/tabs';
function App() {
return (
<ThemeProvider>
{/* Your app content */}
<Tabs>{/* Tabs content */}</Tabs>
</ThemeProvider>
);
}Real-world example: See this setup in action in AC Frontend’s agent app .
Using themed components outside of AC Frontend? Reach out to us in Slack at #help-design-systems to get started!
Using Camp Components
Understanding the themed prop
The themed prop controls whether a component responds to theme changes:
themed={true}: Component responds to theme changes (light/dark mode)themed={false}: Component uses light mode styling only
Component-Specific Behavior
@camp/ai-* Components (AI Components)
AI components default to themed={true} for automatic light/dark mode support:
import { AiButton } from '@camp/ai-button';
// ✅ Default behavior - supports light/dark mode
<AiButton>Click me</AiButton>
// ✅ Explicitly enable theming (same as default)
<AiButton themed>Click me</AiButton>
// ❌ Disable theming - light mode only
<AiButton themed={false}>Click me</AiButton>@camp/* Components (Standard Components)
Standard components default to themed={false} for light mode only:
import { Tabs } from '@camp/tabs';
// ✅ Default behavior - light mode only
<Tabs>
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
</Tabs>
// ✅ Enable theming - supports light/dark mode
<Tabs themed>
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
</Tabs>
// ❌ Explicitly disable theming (same as default)
<Tabs themed={false}>
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
</Tabs>Styled Components Examples with Tokens
Loading...
Best Practices
- Declare styled-components within the same file where they are being used. Modularize as needed for clarity and ease of maintenance.
- Use theme object syntax for all token values, not just colors. This includes:
- Colors (
theme.colors.*) - Spacing (
theme.space.*) - Borders (
theme.border.*) - Typography (`theme.typography.*“)
- And all other available tokens
- Colors (
Example
Here’s a comprehensive example showing best practices for creating and using styled-components with theme tokens:
import React, { useState } from 'react';
import styled from 'styled-components';
import { Textarea } from '@camp/textarea';
// ✅ Declare styled-components in the same file
const EditableTextareaContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.space[4]};
padding: ${({ theme }) => theme.space[4]};
background-color: ${({ theme }) => theme.colors.background.default};
border: ${({ theme }) => theme.border[1]} ${({ theme }) => theme.border.solid}
${({ theme }) => theme.colors.border.default};
border-radius: ${({ theme }) => theme.border.radius[2]};
`;
const EditableTextareaHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: ${({ theme }) => theme.space[2]};
border-bottom: ${({ theme }) => theme.border.width[1]} ${({ theme }) => theme.border.solid}
${({ theme }) => theme.color.border.supportive};
`;
const EditableTextareaTitle = styled.h3`
margin: 0;
font-family: ${({ theme }) => theme.typography.fontFamily.standard};
color: ${({ theme }) => theme.colors.text.default};
`;
const EditableTextareaActions = styled.div`
display: flex;
gap: ${({ theme }) => theme.space[2]};
`;
const EditableTextareaContent = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.space[3]};
`;
const EditableTextareaLabel = styled.label`
font-family: ${({ theme }) => theme.typography.fontFamily.standard};
color: ${({ theme }) => theme.colors.text.primary.default};
`;
const EditableTextareaWrapper = styled.div`
position: relative;
border-radius: ${({ theme }) => theme.border.radius[1]};
&:focus-within {
outline: ${({ theme }) => theme.border.width[2]} ${({ theme }) => theme.border.solid}
${({ theme }) => theme.colors.border.supportive};
outline-offset: ${({ theme }) => theme.space[1]};
}
`;
const EditableTextareaFooter = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
gap: ${({ theme }) => theme.space[2]};
padding-top: ${({ theme }) => theme.space[2]};
border-top: ${({ theme }) => theme.border.width[1]} ${({ theme }) => theme.border.solid}
${({ theme }) => theme.colors.border.supportive};
`;
// Main component using the styled components
interface EditableTextareaProps {
title: string;
label: string;
value: string;
onChange: (value: string) => void;
onSave?: () => void;
onCancel?: () => void;
}
export function EditableTextarea({
title,
label,
value,
onChange,
onSave,
onCancel,
}: EditableTextareaProps) {
const [isEditing, setIsEditing] = useState(false);
return (
<EditableTextareaContainer>
<EditableTextareaHeader>
<EditableTextareaTitle>{title}</EditableTextareaTitle>
<EditableTextareaActions>{/* Action buttons would go here */}</EditableTextareaActions>
</EditableTextareaHeader>
<EditableTextareaContent>
<EditableTextareaLabel>{label}</EditableTextareaLabel>
<EditableTextareaWrapper>
<Textarea
value={value}
onChange={(e) => onChange(e.target.value)}
disabled={!isEditing}
/>
</EditableTextareaWrapper>
</EditableTextareaContent>
{isEditing && (
<EditableTextareaFooter>
{onCancel && <button onClick={onCancel}>Cancel</button>}
{onSave && <button onClick={onSave}>Save</button>}
</EditableTextareaFooter>
)}
</EditableTextareaContainer>
);
}Key Patterns from the Example
-
All tokens use theme object syntax: Notice how spacing, colors, borders, typography, and other properties all reference
theme.*instead of hardcoded values wherever possible. -
Styled-components declared in the same file: All styled components are defined at the top of the file where they’re used, making the code easier to maintain and understand.
-
Modular structure: Components are broken down into logical pieces (
Container,Header,Content,Footer) for clarity. -
Theme-aware styling: All styles automatically adapt to light/dark mode through the theme tokens.
Available Token Categories
When creating styled-components, you can access tokens from these categories:
- Colors:
theme.colors.*(e.g.,theme.colors.background.default,theme.colors.text.default) - Spacing:
theme.space.*(e.g.,theme.space[1],theme.space[4]) - Borders:
theme.border.*(e.g.,theme.border.width[1],theme.border.radius[2]) - Typography:
theme.typography.*(e.g.,theme.typography.fontFamily.standard,theme.typography.fontFamily.display) - And more: See Tokens for a complete list
Troubleshooting
Common Issues
Components not responding to theme changes
Problem: Your themed components aren’t switching between light and dark modes.
Solutions:
- Ensure
ThemeProviderwraps your component - Check that you are using the latest version of the component
- Check that the component supports the
themedprop - Verify
themed={true}is set for@camp/*components - Confirm your app’s theme context is properly configured
- For custom styled-components, ensure you’re using theme object syntax (
theme.color.*) instead of hardcoded values
Styling appears broken
Problem: Components look unstyled or have incorrect colors.
Solutions:
- Verify
ThemeProvideris imported from the correct package - Check that you are using the latest version of the component
- Ensure you’re using a component that requires
ThemeProvider - Try setting
themed={false}to use light mode only - For custom styled-components, verify you’re accessing tokens correctly (e.g.,
theme.color.bg.defaultnottheme.colors.background)
Getting Help
- Check individual component documentation for specific theming requirements
- See Tokens for available theme tokens
- Reach out to us in Slack at #help-design-systems with any questions – we are always happy to help!
Deprecated Approaches
Next Gen styling originally started simple using vanilla CSS and CSS Modules. If you run into code like this and need help, contact our team. Otherwise, we will be transitioning all these components to use styled-components over the next few months.
styled()
styled() was a custom-built CSS-in-JS utility used to create single-purpose React component styles. It is deprecated. But if you are working with it somewhere in the platform, see here for how to use it.