theme-system

CSS custom properties theme architecture for 4 themes (studio, earth, athlete, gradient) with data-theme attribute switching and theme-aware components. Use when implementing theme switching, defining color schemes, or creating theme-responsive UI elements.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "theme-system" with this command: npx skills add canatufkansu/claude-skills/canatufkansu-claude-skills-theme-system

Theme System

Theme Definitions

/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    /* Default: Studio theme */
    --background: 0 0% 100%;
    --foreground: 0 0% 3.9%;
    --card: 0 0% 100%;
    --card-foreground: 0 0% 3.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 0 0% 3.9%;
    --primary: 0 0% 9%;
    --primary-foreground: 0 0% 98%;
    --secondary: 0 0% 96.1%;
    --secondary-foreground: 0 0% 9%;
    --muted: 0 0% 96.1%;
    --muted-foreground: 0 0% 45.1%;
    --accent: 0 0% 96.1%;
    --accent-foreground: 0 0% 9%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 0 0% 98%;
    --border: 0 0% 89.8%;
    --input: 0 0% 89.8%;
    --ring: 0 0% 3.9%;
    --radius: 0.5rem;
  }

  /* Theme: Earth - Soft organic warmth */
  [data-theme="earth"] {
    --background: 40 33% 98%;
    --foreground: 30 10% 15%;
    --card: 40 30% 97%;
    --card-foreground: 30 10% 15%;
    --primary: 30 30% 35%;
    --primary-foreground: 40 30% 98%;
    --secondary: 35 25% 92%;
    --secondary-foreground: 30 20% 25%;
    --muted: 35 20% 93%;
    --muted-foreground: 30 10% 40%;
    --accent: 35 30% 88%;
    --accent-foreground: 30 20% 20%;
    --border: 35 20% 85%;
    --input: 35 20% 88%;
    --ring: 30 30% 35%;
  }

  /* Theme: Studio - Clean minimal */
  [data-theme="studio"] {
    --background: 0 0% 100%;
    --foreground: 0 0% 5%;
    --card: 0 0% 100%;
    --card-foreground: 0 0% 5%;
    --primary: 0 0% 8%;
    --primary-foreground: 0 0% 100%;
    --secondary: 0 0% 97%;
    --secondary-foreground: 0 0% 10%;
    --muted: 0 0% 96%;
    --muted-foreground: 0 0% 40%;
    --accent: 0 0% 95%;
    --accent-foreground: 0 0% 10%;
    --border: 0 0% 92%;
    --input: 0 0% 94%;
    --ring: 0 0% 8%;
  }

  /* Theme: Athlete - Bold with purple accent */
  [data-theme="athlete"] {
    --background: 0 0% 99%;
    --foreground: 270 5% 10%;
    --card: 0 0% 100%;
    --card-foreground: 270 5% 10%;
    --primary: 270 60% 50%;
    --primary-foreground: 0 0% 100%;
    --secondary: 270 20% 95%;
    --secondary-foreground: 270 30% 25%;
    --muted: 270 10% 94%;
    --muted-foreground: 270 5% 40%;
    --accent: 270 40% 92%;
    --accent-foreground: 270 40% 30%;
    --border: 270 10% 88%;
    --input: 270 10% 90%;
    --ring: 270 60% 50%;
  }

  /* Theme: Gradient - Soft gradient hero */
  [data-theme="gradient"] {
    --background: 220 30% 99%;
    --foreground: 220 10% 10%;
    --card: 0 0% 100%;
    --card-foreground: 220 10% 10%;
    --primary: 220 80% 55%;
    --primary-foreground: 0 0% 100%;
    --secondary: 280 30% 95%;
    --secondary-foreground: 220 20% 20%;
    --muted: 220 20% 95%;
    --muted-foreground: 220 10% 40%;
    --accent: 280 40% 93%;
    --accent-foreground: 280 30% 25%;
    --border: 220 15% 90%;
    --input: 220 15% 92%;
    --ring: 220 80% 55%;
    
    /* Gradient-specific variables */
    --gradient-start: 220 80% 60%;
    --gradient-end: 280 60% 65%;
  }
}

Theme Configuration

// lib/theme.ts
export const themes = ['studio', 'earth', 'athlete', 'gradient'] as const;
export type Theme = (typeof themes)[number];
export const defaultTheme: Theme = 'studio';

export function isValidTheme(theme: string): theme is Theme {
  return themes.includes(theme as Theme);
}

Theme Provider

// components/ThemeProvider.tsx
'use client';

import { createContext, useContext, useEffect, useState } from 'react';
import { type Theme, defaultTheme, isValidTheme } from '@/lib/theme';

interface ThemeContextType {
  theme: Theme;
  setTheme: (theme: Theme) => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>(defaultTheme);

  useEffect(() => {
    // Check URL param first
    const params = new URLSearchParams(window.location.search);
    const urlTheme = params.get('theme');
    
    if (urlTheme && isValidTheme(urlTheme)) {
      setTheme(urlTheme);
      document.documentElement.setAttribute('data-theme', urlTheme);
      return;
    }

    // Check localStorage
    const stored = localStorage.getItem('theme');
    if (stored && isValidTheme(stored)) {
      setTheme(stored);
      document.documentElement.setAttribute('data-theme', stored);
    }
  }, []);

  const handleSetTheme = (newTheme: Theme) => {
    setTheme(newTheme);
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
    
    // Update URL without reload
    const url = new URL(window.location.href);
    url.searchParams.set('theme', newTheme);
    window.history.replaceState({}, '', url);
  };

  return (
    <ThemeContext.Provider value={{ theme, setTheme: handleSetTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

Theme Switcher (Dev Mode)

// components/ThemeSwitcher.tsx
'use client';

import { useTheme } from './ThemeProvider';
import { themes, type Theme } from '@/lib/theme';

const themeLabels: Record<Theme, string> = {
  studio: 'Studio',
  earth: 'Earth',
  athlete: 'Athlete',
  gradient: 'Gradient',
};

export function ThemeSwitcher() {
  const { theme, setTheme } = useTheme();

  // Only show in development or with ?dev query param
  if (process.env.NODE_ENV === 'production') {
    return null;
  }

  return (
    <div className="fixed bottom-4 right-4 z-50 bg-card border rounded-lg p-2 shadow-lg">
      <div className="flex gap-1">
        {themes.map((t) => (
          <button
            key={t}
            onClick={() => setTheme(t)}
            className={`px-3 py-1 text-sm rounded transition-colors ${
              theme === t
                ? 'bg-primary text-primary-foreground'
                : 'hover:bg-muted'
            }`}
          >
            {themeLabels[t]}
          </button>
        ))}
      </div>
    </div>
  );
}

Theme-Aware Components

// components/Hero.tsx
'use client';

import { useTheme } from './ThemeProvider';

export function Hero() {
  const { theme } = useTheme();

  return (
    <section
      className={`
        relative min-h-[80vh] flex items-center
        ${theme === 'gradient' ? 'bg-gradient-to-br from-[hsl(var(--gradient-start))] to-[hsl(var(--gradient-end))]' : ''}
        ${theme === 'earth' ? 'bg-[url("/patterns/wave.svg")] bg-cover' : ''}
      `}
    >
      {theme === 'gradient' && (
        <div className="absolute inset-0 backdrop-blur-sm bg-background/30" />
      )}
      
      <div className="relative container mx-auto px-4">
        <h1 className={`
          text-4xl md:text-6xl font-bold
          ${theme === 'gradient' ? 'text-white drop-shadow-lg' : ''}
        `}>
          Pilates & Yoga
        </h1>
      </div>
    </section>
  );
}

Background Ornaments

// components/BackgroundOrnament.tsx
'use client';

import { useTheme } from './ThemeProvider';

export function BackgroundOrnament() {
  const { theme } = useTheme();

  if (theme === 'studio') {
    return null; // Minimal, no ornaments
  }

  if (theme === 'earth') {
    return (
      <div className="absolute inset-0 overflow-hidden pointer-events-none">
        <svg className="absolute -top-20 -right-20 w-96 h-96 text-accent/30">
          {/* Organic wave shape */}
        </svg>
      </div>
    );
  }

  if (theme === 'athlete') {
    return (
      <div className="absolute top-0 left-0 w-full h-2 bg-primary" />
    );
  }

  return null;
}

Layout Integration

// app/[locale]/layout.tsx
import { ThemeProvider } from '@/components/ThemeProvider';
import { ThemeSwitcher } from '@/components/ThemeSwitcher';

export default function LocaleLayout({ children }) {
  return (
    <ThemeProvider>
      {children}
      <ThemeSwitcher />
    </ThemeProvider>
  );
}

Preview via URL

Access themes via query parameter:

  • ?theme=studio - Clean minimal
  • ?theme=earth - Soft organic
  • ?theme=athlete - Bold purple
  • ?theme=gradient - Gradient hero

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

sitemap-robots

No summary provided by upstream source.

Repository SourceNeeds Review
General

next-intl-i18n

No summary provided by upstream source.

Repository SourceNeeds Review
General

json-ld-schemas

No summary provided by upstream source.

Repository SourceNeeds Review