mcpbeat Sign in

Theme System Agent Skill

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.

3k tokens
context cost
the whole folder, loaded on every use
3
files
instructions only
0
copies elsewhere
how many repositories repackaged it
254
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/BlackBeltTechnology/pi-agent-dashboard --skill theme-system

What comes with it

3 537 bytes besides the instruction
SKILL.agent.md
SKILL.md.AGENTS.md

The instruction itself

9 sections, as written by the author

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

How to use it

Copy the folder

Take blackbelttechnology/theme-system from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.