Load PROACTIVELY when task involves CSS architecture, theming, or visual design systems. Use when user says "set up Tailwind", "add dark mode", "create a design system", "make it responsive", or "configure the theme". Covers Tailwind configuration and custom plugins, design token systems, responsive breakpoint strategies, dark mode implementation, component variant patterns (CVA/class-variance-authority), CSS-in-JS alternatives, and animation patterns.
Guides CSS architecture decisions and implements styling systems including Tailwind, design tokens, dark mode, and responsive patterns.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/styling-comparison.mdscripts/validate-styling.shscripts/
validate-styling.sh
references/
styling-comparison.md
This skill guides you through CSS architecture decisions and implementation using GoodVibes precision tools. Use this workflow when setting up styling infrastructure, creating design systems, or implementing theming and responsive patterns.
Load this skill when:
Trigger phrases: "setup styling", "add Tailwind", "implement dark mode", "design tokens", "responsive design", "CSS architecture", "theme system".
Before making styling decisions, understand the project's current state.
Use discover to find styling patterns across the codebase.
discover:
queries:
- id: tailwind_usage
type: grep
pattern: "(className=|class=).*\".*\\b(flex|grid|text-|bg-|p-|m-)"
glob: "**/*.{tsx,jsx,vue,svelte}"
- id: css_modules
type: glob
patterns: ["**/*.module.css", "**/*.module.scss"]
- id: css_in_js
type: grep
pattern: "(styled\\.|styled\\(|css`|makeStyles|createStyles)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: design_tokens
type: glob
patterns: ["**/tokens.{ts,js,json}", "**/design-tokens.{ts,js,json}", "**/theme.{ts,js}"]
- id: dark_mode
type: grep
pattern: "(dark:|data-theme|ThemeProvider|useTheme|darkMode)"
glob: "**/*.{ts,tsx,js,jsx,css,scss}"
verbosity: count_only
What this reveals:
Read existing config to understand the setup.
precision_read:
files:
- path: "tailwind.config.js"
extract: content
- path: "tailwind.config.ts"
extract: content
- path: "postcss.config.js"
extract: content
- path: "src/styles/globals.css"
extract: outline
verbosity: minimal
Read representative components to understand conventions.
precision_read:
files:
- path: "src/components/Button.tsx" # or discovered component
extract: content
- path: "src/components/Card.tsx"
extract: content
output:
max_per_item: 100
verbosity: standard
Choose the styling approach that fits your project needs. See references/styling-comparison.md for the complete decision tree.
Use Tailwind CSS when:
Use CSS Modules when:
Use CSS-in-JS when:
Use Vanilla CSS when:
For detailed framework-specific patterns, see references/styling-comparison.md.
Based on your chosen approach, install required packages.
Tailwind CSS:
precision_exec:
commands:
- cmd: "npm install -D tailwindcss postcss autoprefixer"
expect:
exit_code: 0
- cmd: "npx tailwindcss init -p"
expect:
exit_code: 0
verbosity: minimal
CSS-in-JS (styled-components):
precision_exec:
commands:
- cmd: "npm install styled-components"
- cmd: "npm install -D @types/styled-components"
verbosity: minimal
Write config files following best practices.
Tailwind Config Example:
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
darkMode: 'class', // or 'media' for system preference
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
// ... full scale
900: '#0c4a6e',
},
},
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
},
spacing: {
'18': '4.5rem',
},
},
},
plugins: [],
};
export default config;
Best Practices:
class strategy for dark mode (more control)precision_write:
files:
- path: "tailwind.config.ts"
content: |
import type { Config } from 'tailwindcss';
// ... [full config]
- path: "src/styles/globals.css"
content: |
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
/* ... design tokens */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}
verbosity: count_only
Create a centralized token system for consistency.
Token Categories:
Implementation Pattern:
// src/styles/tokens.ts
export const tokens = {
colors: {
primary: {
light: '#3b82f6',
DEFAULT: '#2563eb',
dark: '#1d4ed8',
},
semantic: {
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b',
},
},
spacing: {
xs: '0.25rem', // 4px
sm: '0.5rem', // 8px
md: '1rem', // 16px
lg: '1.5rem', // 24px
xl: '2rem', // 32px
},
typography: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
},
},
} as const;
export type Tokens = typeof tokens;
// Integration with ThemeProvider (React Context)
type Theme = {
tokens: Tokens;
mode: 'light' | 'dark';
};
// Integration with styled-components
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme extends Tokens {
mode: 'light' | 'dark';
}
}
// Usage in components
import { useTheme } from 'styled-components';
const Button = styled.button`
background: ${props => props.theme.colors.primary};
padding: ${props => props.theme.spacing[4]};
`;
// tailwind.config.ts
import { tokens } from './src/styles/tokens';
const config: Config = {
theme: {
extend: {
colors: tokens.colors,
spacing: tokens.spacing,
fontFamily: tokens.typography.fontFamily,
fontSize: tokens.typography.fontSize,
},
},
};
Class-based (Recommended):
Media query-based:
// src/components/ThemeProvider.tsx
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import type { ReactNode } from 'react';
interface ThemeProviderProps {
children: ReactNode;
}
export function ThemeProvider({ children }: ThemeProviderProps) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</NextThemesProvider>
);
}
Use CSS variables for seamless theme switching.
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
}
}
Best Practices:
Mobile-First Approach (Recommended):
/* Base styles for mobile */
.container {
padding: 1rem;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
padding: 2rem;
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.container {
padding: 3rem;
}
}
Tailwind Breakpoints:
<div className="p-4 md:p-8 lg:p-12">
{/* padding increases with screen size */}
</div>
Use container queries for component-level responsiveness.
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Tailwind Container Queries:
// tailwind.config.ts
plugins: [require('@tailwindcss/container-queries')]
<div className="@container">
<div className="@md:grid @md:grid-cols-2">
{/* Responsive to container, not viewport */}
</div>
</div>
precision_exec:
commands:
- cmd: "npm install class-variance-authority clsx tailwind-merge"
verbosity: minimal
// src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// src/components/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import type { ButtonHTMLAttributes } from 'react';
const buttonVariants = cva(
// Base styles
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent',
ghost: 'hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
export function Button({
className,
variant,
size,
...props
}: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
Benefits:
// src/components/Input.tsx
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const inputVariants = cva(
'w-full rounded-md border px-3 py-2 text-sm transition-colors',
{
variants: {
state: {
default: 'border-input focus:border-primary focus:ring-2 focus:ring-primary/20',
error: 'border-destructive focus:border-destructive focus:ring-2 focus:ring-destructive/20',
success: 'border-green-500 focus:border-green-500 focus:ring-2 focus:ring-green-500/20',
},
},
defaultVariants: {
state: 'default',
},
}
);
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
error?: string;
success?: boolean;
}
export function Input({ error, success, className, ...props }: InputProps) {
const state = error ? 'error' : success ? 'success' : 'default';
return (
<div className="space-y-1">
<input
className={cn(inputVariants({ state }), className)}
aria-invalid={!!error}
aria-describedby={error ? 'input-error' : undefined}
{...props}
/>
{error && (
<p id="input-error" className="text-sm text-destructive" role="alert">
{error}
</p>
)}
</div>
);
}
// src/components/Toast.tsx
import { cva } from 'class-variance-authority';
const toastVariants = cva(
'rounded-lg border p-4 shadow-lg',
{
variants: {
variant: {
default: 'bg-background border-border',
success: 'bg-green-50 border-green-200 text-green-900 dark:bg-green-950 dark:border-green-800 dark:text-green-100',
error: 'bg-red-50 border-red-200 text-red-900 dark:bg-red-950 dark:border-red-800 dark:text-red-100',
warning: 'bg-yellow-50 border-yellow-200 text-yellow-900 dark:bg-yellow-950 dark:border-yellow-800 dark:text-yellow-100',
},
},
}
);
/* Skeleton pulse animation */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.skeleton {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
background-color: hsl(var(--muted));
border-radius: 0.375rem;
}
// src/components/Skeleton.tsx
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('skeleton', className)} {...props} />;
}
// Usage in loading states
export function CardSkeleton() {
return (
<div className="space-y-3">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-20 w-full" />
</div>
);
}
.button {
transition: background-color 200ms ease-in-out;
}
.button:hover {
background-color: var(--primary-hover);
}
Tailwind:
<button className="transition-colors duration-200 hover:bg-primary-hover">
Click me
</button>
For complex animations and gestures.
// src/components/AnimatedCard.tsx
import { motion } from 'framer-motion';
export function AnimatedCard() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<div className="card">Content</div>
</motion.div>
);
}
Best Practices:
prefers-reduced-motionbash scripts/validate-styling.sh .
See scripts/validate-styling.sh for the complete validation suite.
Verify CSS bundle size and ensure no unused styles.
precision_exec:
commands:
- cmd: "npm run build"
expect:
exit_code: 0
verbosity: standard
Check for:
Ensure color contrast meets WCAG standards.
precision_exec:
commands:
- cmd: "npx @axe-core/cli http://localhost:3000"
verbosity: standard
Catch unintended visual changes with automated screenshot comparison.
Playwright with Visual Testing:
// tests/visual.spec.ts
import { test, expect } from '@playwright/test';
test('button variants match snapshots', async ({ page }) => {
await page.goto('/components/button');
// Take screenshot and compare
await expect(page).toHaveScreenshot('button-variants.png', {
maxDiffPixels: 100,
});
});
test('dark mode toggle', async ({ page }) => {
await page.goto('/');
// Light mode
await expect(page).toHaveScreenshot('home-light.png');
// Toggle to dark
await page.click('[data-testid="theme-toggle"]');
await expect(page).toHaveScreenshot('home-dark.png');
});
Chromatic (for Storybook):
# Install and setup
npm install --save-dev chromatic
# Run visual tests
npx chromatic --project-token=<token>
Percy (cross-browser):
import percySnapshot from '@percy/playwright';
test('responsive layout', async ({ page }) => {
await page.goto('/dashboard');
await percySnapshot(page, 'Dashboard - Desktop');
await page.setViewportSize({ width: 375, height: 667 });
await percySnapshot(page, 'Dashboard - Mobile');
});
Tailwind v3+ with JIT:
The content paths in tailwind.config.ts serve as the purge configuration. Tailwind automatically removes unused classes in production builds.
// tailwind.config.ts
export default {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx}',
'./src/components/**/*.{js,ts,jsx,tsx}',
'./src/app/**/*.{js,ts,jsx,tsx}',
],
// JIT mode is default in v3+
};
Production Build Optimization:
# Build with minification
npx tailwindcss -i ./src/styles/globals.css -o ./dist/output.css --minify
# Check final size
du -h dist/output.css
Safelist Dynamic Classes:
// tailwind.config.ts
export default {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
safelist: [
// Dynamic classes that can't be detected statically
'bg-red-500',
'bg-green-500',
'bg-blue-500',
// Or use patterns
{
pattern: /bg-(red|green|blue)-(400|500|600)/,
},
],
};
Monitor Bundle Size:
precision_exec:
commands:
- cmd: "npm run build"
- cmd: "ls -lh dist/**/*.css"
verbosity: standard
Target: <50KB gzipped for typical Tailwind projects after purging.
DON'T:
!important to override Tailwind (fix specificity instead)DO:
CSP (Content Security Policy) and Styling:
Safe by Default (Build-time):
Requires CSP Configuration (Runtime):
<style> tags at runtimeunsafe-inline or nonceCSP Best Practices:
// Next.js example with CSP for styled-components
import { getCspNonce } from './lib/csp';
export default function RootLayout({ children }) {
const nonce = getCspNonce();
return (
<html>
<head>
<meta
httpEquiv="Content-Security-Policy"
content={`style-src 'self' 'nonce-${nonce}';`}
/>
</head>
<body>
<StyleSheetManager nonce={nonce}>
{children}
</StyleSheetManager>
</body>
</html>
);
}
CSS Injection Prevention:
// DON'T: Unsafe user input interpolation
const BadButton = ({ userColor }) => (
<div style={{ backgroundColor: userColor }} /> // CSS injection risk!
);
// DO: Validate against allowlist
const ALLOWED_COLORS = ['primary', 'secondary', 'accent'] as const;
type AllowedColor = typeof ALLOWED_COLORS[number];
const SafeButton = ({ color }: { color: AllowedColor }) => (
<div className={`bg-${color}`} />
);
// DO: Validate hex colors
function isValidHexColor(color: string): boolean {
return /^#[0-9A-Fa-f]{6}$/.test(color);
}
Discovery Phase:
discover: { queries: [tailwind, css_modules, css_in_js, tokens, dark_mode], verbosity: count_only }
precision_read: { files: [tailwind.config.ts, globals.css], verbosity: minimal }
Configuration Phase:
precision_exec: { commands: [{ cmd: "npm install -D tailwindcss postcss autoprefixer" }] }
precision_write: { files: [tailwind.config.ts, globals.css, tokens.ts], verbosity: count_only }
Validation Phase:
bash scripts/validate-styling.sh .
npm run build
For detailed decision trees, token examples, and framework-specific patterns, see references/styling-comparison.md.
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.