or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-styling.mdcss-utilities.mdindex.mdreact-native.mdserver-side-rendering.mdtest-utilities.mdtheming.mdtypescript-integration.md
tile.json

theming.mddocs/

Theming

styled-components provides a comprehensive theming system built on React Context, enabling consistent design systems across applications with theme providers, consumers, and utilities.

ThemeProvider

The ThemeProvider component makes a theme available to all styled components in the component tree via React Context.

declare const ThemeProvider: React.ComponentType<{
  theme: DefaultTheme | ((outerTheme?: DefaultTheme) => DefaultTheme);
  children?: React.ReactNode;
}>;

interface DefaultTheme {}

Usage Examples:

Basic Theme Provider

import { ThemeProvider } from 'styled-components';

const theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    success: '#28a745',
    danger: '#dc3545',
    warning: '#ffc107',
    info: '#17a2b8',
    light: '#f8f9fa',
    dark: '#343a40',
  },
  fonts: {
    main: '"Helvetica Neue", Helvetica, Arial, sans-serif',
    mono: '"Fira Code", "Courier New", monospace',
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
  },
  breakpoints: {
    mobile: '768px',
    tablet: '1024px',
    desktop: '1200px',
  },
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <MainLayout />
    </ThemeProvider>
  );
}

Function-based Themes

// Theme that extends parent theme
const darkTheme = (outerTheme) => ({
  ...outerTheme,
  colors: {
    ...outerTheme.colors,
    background: '#1a1a1a',
    text: '#ffffff',
    surface: '#2d2d2d',
  },
});

function DarkModeWrapper({ children }) {
  return (
    <ThemeProvider theme={darkTheme}>
      {children}
    </ThemeProvider>
  );
}

Nested Theme Providers

const baseTheme = {
  colors: { primary: '#007bff' },
  spacing: { md: '16px' }
};

const buttonTheme = (outerTheme) => ({
  ...outerTheme,
  button: {
    borderRadius: '4px',
    fontSize: '14px',
  }
});

function App() {
  return (
    <ThemeProvider theme={baseTheme}>
      <Header />
      <main>
        <ThemeProvider theme={buttonTheme}>
          <ButtonSection />
        </ThemeProvider>
      </main>
    </ThemeProvider>
  );
}

Theme Access Methods

useTheme Hook

React hook for accessing the current theme in functional components.

function useTheme(): DefaultTheme;

Usage Examples:

import { useTheme } from 'styled-components';

function ThemedComponent() {
  const theme = useTheme(); // Throws error if no ThemeProvider found
  
  return (
    <div style={{ 
      backgroundColor: theme.colors.primary,
      padding: theme.spacing.md 
    }}>
      Themed content
    </div>
  );
}

// Safe usage with error boundary or conditional rendering
function ConditionalThemedComponent() {
  try {
    const theme = useTheme();
    return (
      <div style={{ 
        backgroundColor: theme.colors.primary,
        padding: theme.spacing.md 
      }}>
        Themed content
      </div>
    );
  } catch (error) {
    return <div>No theme available</div>;
  }
}

// Custom hook with type assertion
function useAppTheme() {
  const theme = useTheme(); // Already throws if no theme
  return theme as AppTheme;
}

withTheme HOC

Higher-order component that injects the theme as a prop.

function withTheme<T extends React.ComponentType<any>>(
  Component: T
): React.ForwardRefExoticComponent<
  React.PropsWithoutRef<React.ComponentProps<T>> & React.RefAttributes<T>
>;

Usage Examples:

import { withTheme } from 'styled-components';

interface Props {
  title: string;
  theme: DefaultTheme;
}

function ThemedHeader({ title, theme }: Props) {
  return (
    <h1 style={{ 
      color: theme.colors.primary,
      fontFamily: theme.fonts.main 
    }}>
      {title}
    </h1>
  );
}

const ThemedHeaderWithTheme = withTheme(ThemedHeader);

// Usage - theme prop is automatically injected
<ThemedHeaderWithTheme title="Welcome" />

ThemeContext and ThemeConsumer

Direct access to the theme context for advanced use cases.

declare const ThemeContext: React.Context<DefaultTheme | undefined>;
declare const ThemeConsumer: React.Consumer<DefaultTheme | undefined>;

Usage Examples:

import { ThemeContext, ThemeConsumer } from 'styled-components';

// Using ThemeContext directly
function DirectThemeAccess() {
  const theme = React.useContext(ThemeContext);
  return <div>Current primary color: {theme?.colors.primary}</div>;
}

// Using ThemeConsumer
function ThemeConsumerExample() {
  return (
    <ThemeConsumer>
      {theme => (
        <div style={{ backgroundColor: theme?.colors.background }}>
          Consumer content
        </div>
      )}
    </ThemeConsumer>
  );
}

Using Themes in Styled Components

Basic Theme Usage

const Button = styled.button`
  background-color: ${props => props.theme.colors.primary};
  color: white;
  padding: ${props => props.theme.spacing.md};
  font-family: ${props => props.theme.fonts.main};
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  &:hover {
    background-color: ${props => props.theme.colors.primaryHover || props.theme.colors.primary};
    opacity: 0.9;
  }
`;

Theme-based Variants

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
}

const Button = styled.button<ButtonProps>`
  background-color: ${props => {
    switch (props.variant) {
      case 'primary': return props.theme.colors.primary;
      case 'secondary': return props.theme.colors.secondary;
      case 'danger': return props.theme.colors.danger;
      default: return props.theme.colors.primary;
    }
  }};
  
  padding: ${props => {
    const spacing = props.theme.spacing;
    switch (props.size) {
      case 'small': return `${spacing.xs} ${spacing.sm}`;
      case 'large': return `${spacing.lg} ${spacing.xl}`;
      default: return `${spacing.sm} ${spacing.md}`;
    }
  }};
  
  font-size: ${props => {
    switch (props.size) {
      case 'small': return '12px';
      case 'large': return '18px';
      default: return '14px';
    }
  }};
  
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-family: ${props => props.theme.fonts.main};
`;

Responsive Theming

const ResponsiveContainer = styled.div`
  padding: ${props => props.theme.spacing.md};
  
  @media (max-width: ${props => props.theme.breakpoints.mobile}) {
    padding: ${props => props.theme.spacing.sm};
  }
  
  @media (min-width: ${props => props.theme.breakpoints.desktop}) {
    padding: ${props => props.theme.spacing.xl};
  }
`;

TypeScript Theme Integration

Theme Interface Declaration

// styled-components.d.ts
import 'styled-components';

interface AppTheme {
  colors: {
    primary: string;
    secondary: string;
    background: string;
    text: string;
    error: string;
    success: string;
  };
  fonts: {
    main: string;
    mono: string;
  };
  spacing: {
    xs: string;
    sm: string;
    md: string;
    lg: string;
    xl: string;
  };
  breakpoints: {
    mobile: string;
    tablet: string;
    desktop: string;
  };
  zIndex: {
    dropdown: number;
    modal: number;
    tooltip: number;
  };
}

declare module 'styled-components' {
  export interface DefaultTheme extends AppTheme {}
}

Typed Theme Usage

// Now all styled components have full TypeScript support for theme
const TypedButton = styled.button`
  background-color: ${props => props.theme.colors.primary}; // ✅ Type-safe
  padding: ${props => props.theme.spacing.md}; // ✅ Type-safe
  z-index: ${props => props.theme.zIndex.dropdown}; // ✅ Type-safe
`;

// TypeScript will catch errors
const ErrorButton = styled.button`
  background-color: ${props => props.theme.colors.primaryColor}; // ❌ TypeScript error
`;

Advanced Theming Patterns

Theme Utilities

// Theme utility functions
export const getColor = (theme: DefaultTheme, color: keyof DefaultTheme['colors']) => {
  return theme.colors[color];
};

export const getSpacing = (theme: DefaultTheme, size: keyof DefaultTheme['spacing']) => {
  return theme.spacing[size];
};

export const media = {
  mobile: (content: TemplateStringsArray) => css`
    @media (max-width: ${props => props.theme.breakpoints.mobile}) {
      ${content}
    }
  `,
  tablet: (content: TemplateStringsArray) => css`
    @media (max-width: ${props => props.theme.breakpoints.tablet}) {
      ${content}
    }
  `,
  desktop: (content: TemplateStringsArray) => css`
    @media (min-width: ${props => props.theme.breakpoints.desktop}) {
      ${content}
    }
  `,
};

// Usage
const UtilityButton = styled.button`
  background-color: ${props => getColor(props.theme, 'primary')};
  padding: ${props => getSpacing(props.theme, 'md')};
  
  ${media.mobile`
    font-size: 14px;
  `}
`;

Theme Switching

const lightTheme: AppTheme = {
  colors: {
    background: '#ffffff',
    text: '#333333',
    primary: '#007bff',
    // ...
  },
  // ...
};

const darkTheme: AppTheme = {
  colors: {
    background: '#1a1a1a',
    text: '#ffffff',
    primary: '#4dabf7',
    // ...
  },
  // ...
};

function App() {
  const [isDark, setIsDark] = React.useState(false);
  
  return (
    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
      <GlobalStyle />
      <ThemeToggle onClick={() => setIsDark(!isDark)} />
      <MainContent />
    </ThemeProvider>
  );
}

Theme Context Composition

// Custom theme hook with fallbacks
function useAppTheme() {
  const theme = useTheme();
  
  // Provide default theme if none exists
  const defaultTheme: AppTheme = {
    colors: { /* defaults */ },
    fonts: { /* defaults */ },
    // ...
  };
  
  return theme || defaultTheme;
}

// Higher-order component for theme requirements
function withRequiredTheme<Props>(Component: React.ComponentType<Props>) {
  return function ThemedComponent(props: Props) {
    const theme = useTheme();
    
    if (!theme) {
      throw new Error('Component requires a theme but no ThemeProvider was found');
    }
    
    return <Component {...props} />;
  };
}

Dynamic Theme Loading

interface ThemeProviderProps {
  children: React.ReactNode;
  themeName?: string;
}

const themes = {
  light: lightTheme,
  dark: darkTheme,
  blue: blueTheme,
  green: greenTheme,
};

function DynamicThemeProvider({ children, themeName = 'light' }: ThemeProviderProps) {
  const [currentTheme, setCurrentTheme] = React.useState(themes[themeName]);
  
  React.useEffect(() => {
    setCurrentTheme(themes[themeName] || themes.light);
  }, [themeName]);
  
  return (
    <ThemeProvider theme={currentTheme}>
      {children}
    </ThemeProvider>
  );
}