Agent Skills: CSS Modules

|

UncategorizedID: madappgang/claude-code/css-modules

Install this agent skill to your local

pnpm dlx add-skill https://github.com/MadAppGang/claude-code/tree/HEAD/plugins/dev/skills/frontend/css-modules

Skill Files

Browse the full folder contents for css-modules.

Download Skill

Loading file tree…

plugins/dev/skills/frontend/css-modules/SKILL.md

Skill Metadata

Name
css-modules
Description
|

CSS Modules

Overview

CSS Modules provide locally-scoped CSS by automatically generating unique class names at build time. This prevents style conflicts and enables true component encapsulation.

When to Use CSS Modules

| Use Case | CSS Modules | Tailwind | |----------|-------------|----------| | Complex animations | Best | Good | | Third-party component styling | Best | Harder | | Legacy CSS migration | Best | Refactor needed | | Rapid prototyping | Slower | Best | | Design system utilities | Not ideal | Best | | Component encapsulation | Best | N/A | | Team with CSS expertise | Best | Either |

Hybrid Approach: Use both together - Tailwind for utilities, CSS Modules for complex components.


Documentation Index

Vite Integration

| Topic | URL | Description | |-------|-----|-------------| | CSS Modules in Vite | https://vite.dev/guide/features#css-modules | Vite's built-in support | | Lightning CSS | https://vite.dev/guide/features#lightning-css | Fast CSS transforms | | PostCSS | https://vite.dev/guide/features#postcss | PostCSS configuration |

Lightning CSS

| Topic | URL | Description | |-------|-----|-------------| | Documentation | https://lightningcss.dev/docs.html | Official docs | | CSS Modules | https://lightningcss.dev/css-modules.html | Module support | | Transpilation | https://lightningcss.dev/transpilation.html | Browser targeting | | Bundling | https://lightningcss.dev/bundling.html | CSS bundling |

CSS Modules Spec

| Topic | URL | Description | |-------|-----|-------------| | CSS Modules | https://github.com/css-modules/css-modules | Specification | | Composition | https://github.com/css-modules/css-modules#composition | Composing classes | | Scoping | https://github.com/css-modules/css-modules#naming | Local vs global |

TypeScript Integration

| Topic | URL | Description | |-------|-----|-------------| | typed-css-modules | https://github.com/Quramy/typed-css-modules | Generate .d.ts files | | vite-plugin-css-modules-dts | https://github.com/mrcjkb/vite-plugin-css-modules-dts | Vite plugin |


CSS Modules Fundamentals

File Naming Convention

src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.module.css      # CSS Module
│   │   └── Button.test.tsx
│   └── Card/
│       ├── Card.tsx
│       ├── Card.module.css        # CSS Module
│       └── index.ts

Files ending in .module.css are automatically processed as CSS Modules.

Basic Usage

/* Button.module.css */
.button {
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  font-weight: 500;
  transition: background-color 150ms ease;
}

.primary {
  background-color: hsl(221, 83%, 53%);
  color: white;
}

.primary:hover {
  background-color: hsl(224, 76%, 48%);
}

.secondary {
  background-color: hsl(0, 0%, 96%);
  color: hsl(0, 0%, 9%);
}
// Button.tsx
import styles from './Button.module.css'

interface ButtonProps {
  variant?: 'primary' | 'secondary'
  children: React.ReactNode
}

export function Button({ variant = 'primary', children }: ButtonProps) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      {children}
    </button>
  )
}

Generated Class Names

<!-- Input -->
<button class="${styles.button} ${styles.primary}">

<!-- Output (generated) -->
<button class="Button_button_x7d9f Button_primary_a3k2j">

Local vs Global Scope

/* Local by default */
.button {
  /* Generates: Button_button_hash */
}

/* Explicit local */
:local(.button) {
  /* Same as above */
}

/* Global (escape hatch) */
:global(.external-library-class) {
  /* Kept as-is: .external-library-class */
}

/* Global within local */
.card :global(.markdown-body) {
  /* Scoped parent, global child */
}

Composition

composes Keyword

Share styles between classes:

/* base.module.css */
.flexCenter {
  display: flex;
  align-items: center;
  justify-content: center;
}

.interactive {
  cursor: pointer;
  transition: all 150ms ease;
}
/* Button.module.css */
.button {
  composes: flexCenter from './base.module.css';
  composes: interactive from './base.module.css';
  padding: 0.5rem 1rem;
}

Multiple Compositions

.primaryButton {
  composes: button;
  composes: primary from './colors.module.css';
  composes: rounded from './shapes.module.css';
}

Usage in React

// Composed class automatically includes all composed classes
<button className={styles.primaryButton}>
  {/* Renders: Button_primaryButton_x Button_button_y colors_primary_z shapes_rounded_w */}
</button>

Vite Configuration

Lightning CSS (Recommended)

Lightning CSS is 100x faster than PostCSS for transforms:

npm install lightningcss browserslist
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import browserslistToTargets from 'lightningcss/browserslist'
import browserslist from 'browserslist'

export default defineConfig({
  plugins: [react()],
  css: {
    transformer: 'lightningcss',
    lightningcss: {
      targets: browserslistToTargets(browserslist('>= 0.25%')),
      cssModules: {
        // Class name pattern
        pattern: '[name]__[local]_[hash:5]',
        // Or for production:
        // pattern: '[hash:8]'
      }
    }
  },
  build: {
    cssMinify: 'lightningcss'
  }
})

Class Name Patterns

| Pattern | Example Output | |---------|----------------| | [name]__[local]_[hash:5] | Button__primary_a3k2j | | [local]_[hash:8] | primary_a3k2j9x1 | | [hash:8] | a3k2j9x1 (production) |

Lightning CSS Features (Included)

Lightning CSS automatically handles:

  • Vendor prefixing (replaces autoprefixer)
  • Modern syntax transpilation
  • Nesting
  • Custom media queries
  • Color functions (oklch, lab, lch)

PostCSS Configuration (When Needed)

For plugins Lightning CSS doesn't support:

// postcss.config.js
export default {
  plugins: {
    'postcss-import': {},
    'postcss-custom-media': {},
    // Don't use: autoprefixer (Lightning CSS handles this)
    // Don't use: postcss-nested (Lightning CSS handles this)
  }
}

Note: Use Lightning CSS for transforms, PostCSS only for unsupported plugins.


TypeScript Integration

Type Declarations

Without types, TypeScript doesn't know the shape of CSS modules:

// This would error without declarations
import styles from './Button.module.css'
styles.button  // TS error: Property 'button' does not exist

Option 1: Wildcard Declaration (Simple)

// src/vite-env.d.ts or src/types/css-modules.d.ts
declare module '*.module.css' {
  const classes: { [key: string]: string }
  export default classes
}

Pros: No extra tooling Cons: No autocomplete, no type safety

Option 2: Generated Declarations (Recommended)

npm install -D vite-plugin-css-modules-dts
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import cssModulesDts from 'vite-plugin-css-modules-dts'

export default defineConfig({
  plugins: [
    react(),
    cssModulesDts({
      // Generate .d.ts next to .module.css files
      outputDir: '.',
    })
  ]
})

Generated files:

// Button.module.css.d.ts (auto-generated)
declare const styles: {
  readonly button: string
  readonly primary: string
  readonly secondary: string
}
export default styles

Pros: Full autocomplete, type safety, catches typos Cons: Generated files in source (add to .gitignore)

Option 3: CLI Generation

npm install -D typed-css-modules

# Generate declarations
npx tcm src --pattern '**/*.module.css'

# Watch mode
npx tcm src --pattern '**/*.module.css' --watch

Add to package.json:

{
  "scripts": {
    "css:types": "tcm src --pattern '**/*.module.css'",
    "css:types:watch": "tcm src --pattern '**/*.module.css' --watch"
  }
}

Patterns

Component-Scoped Styling

/* Card.module.css */
.card {
  border-radius: 0.5rem;
  background: white;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

.header {
  padding: 1rem;
  border-bottom: 1px solid hsl(0, 0%, 90%);
}

.content {
  padding: 1.5rem;
}

.footer {
  padding: 1rem;
  background: hsl(0, 0%, 98%);
}
// Card.tsx
import styles from './Card.module.css'

export function Card({ children }: { children: React.ReactNode }) {
  return <div className={styles.card}>{children}</div>
}

Card.Header = ({ children }: { children: React.ReactNode }) => (
  <header className={styles.header}>{children}</header>
)

Card.Content = ({ children }: { children: React.ReactNode }) => (
  <div className={styles.content}>{children}</div>
)

Card.Footer = ({ children }: { children: React.ReactNode }) => (
  <footer className={styles.footer}>{children}</footer>
)

CSS Variables with Modules

/* theme.css (global) */
:root {
  --color-primary: hsl(221, 83%, 53%);
  --color-primary-dark: hsl(224, 76%, 48%);
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
}

/* Button.module.css */
.button {
  background-color: var(--color-primary);
  padding: var(--spacing-sm) var(--spacing-md);
}

.button:hover {
  background-color: var(--color-primary-dark);
}

Theming with CSS Variables

/* theme.module.css */
.light {
  --bg: white;
  --text: hsl(0, 0%, 9%);
  --border: hsl(0, 0%, 90%);
}

.dark {
  --bg: hsl(0, 0%, 9%);
  --text: hsl(0, 0%, 98%);
  --border: hsl(0, 0%, 20%);
}

/* Component.module.css */
.component {
  background-color: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
}

Complex Animations

CSS Modules excel at complex animations:

/* Modal.module.css */
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 200ms ease;
}

.overlayVisible {
  composes: overlay;
  opacity: 1;
}

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.95);
  opacity: 0;
  transition: all 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

.modalVisible {
  composes: modal;
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
}

@keyframes slideIn {
  from {
    transform: translate(-50%, -50%) translateY(20px) scale(0.95);
    opacity: 0;
  }
  to {
    transform: translate(-50%, -50%) translateY(0) scale(1);
    opacity: 1;
  }
}

.modalAnimated {
  animation: slideIn 300ms cubic-bezier(0.16, 1, 0.3, 1);
}

Hybrid Approach: CSS Modules + Tailwind

Use both together for maximum flexibility:

import styles from './ComplexCard.module.css'

function ComplexCard({ title, children }: Props) {
  return (
    // Tailwind for layout, CSS Module for complex styles
    <div className={`${styles.card} p-4 md:p-6`}>
      <h2 className={`${styles.title} text-lg font-semibold mb-2`}>
        {title}
      </h2>
      <div className={styles.animatedContent}>
        {children}
      </div>
    </div>
  )
}

When to Use Which

| Scenario | Approach | |----------|----------| | Layout utilities (flex, grid, spacing) | Tailwind | | Responsive utilities | Tailwind | | State variants (hover, focus) | Tailwind | | Complex animations | CSS Modules | | Keyframe animations | CSS Modules | | Third-party component overrides | CSS Modules | | Component state classes | CSS Modules | | Design system utilities | Tailwind | | One-off complex styles | CSS Modules |


Performance Benefits

Lightning CSS Advantages

| Feature | Speed Improvement | |---------|-------------------| | CSS parsing | 100x faster than PostCSS | | Vendor prefixing | Built-in, instant | | Minification | Faster than cssnano | | Bundling | Parallel processing |

Dead Code Elimination

Vite automatically eliminates unused CSS:

  • CSS Modules are naturally tree-shaken (only imported classes included)
  • Lightning CSS removes unused selectors

Bundle Size Optimization

// vite.config.ts
export default defineConfig({
  build: {
    cssMinify: 'lightningcss',
    cssCodeSplit: true,  // Separate CSS per chunk
  }
})

Related Skills

  • tailwindcss - Utility-first CSS (complementary approach)
  • shadcn-ui - Component library using CSS variables
  • react-typescript - Component patterns with className
  • testing-frontend - Testing styled components