Skip to Content
DocumentationGuidesDevelopersStyling

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 TypeThemeProvider RequiredDefault Themed Behavior
@camp/ai-*✅ Alwaysthemed={true} (dark mode enabled)
@camp/* (themed)✅ Requiredthemed={false} (light mode only)
@camp/* (standard)❌ Not neededNo 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

  1. Declare styled-components within the same file where they are being used. Modularize as needed for clarity and ease of maintenance.
  2. 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

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

  1. All tokens use theme object syntax: Notice how spacing, colors, borders, typography, and other properties all reference theme.* instead of hardcoded values wherever possible.

  2. 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.

  3. Modular structure: Components are broken down into logical pieces (Container, Header, Content, Footer) for clarity.

  4. 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:

  1. Ensure ThemeProvider wraps your component
  2. Check that you are using the latest version of the component
  3. Check that the component supports the themed prop
  4. Verify themed={true} is set for @camp/* components
  5. Confirm your app’s theme context is properly configured
  6. 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:

  1. Verify ThemeProvider is imported from the correct package
  2. Check that you are using the latest version of the component
  3. Ensure you’re using a component that requires ThemeProvider
  4. Try setting themed={false} to use light mode only
  5. For custom styled-components, verify you’re accessing tokens correctly (e.g., theme.color.bg.default not theme.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.

Last updated on