AgentSkillsCN

css-modules

借助 Lightning CSS 和 PostCSS 实现 CSS 模块化,为组件范围内的样式设计保驾护航。 它涵盖 *.module.css 模式、TypeScript 集成、Vite 配置,以及组件间的复合应用。 当您构建复杂的动画效果、为第三方组件进行样式定制,或迁移遗留 CSS 时,此功能将助您事半功倍。

SKILL.md
--- frontmatter
name: css-modules
description: |
  CSS Modules with Lightning CSS and PostCSS for component-scoped styling.
  Covers *.module.css patterns, TypeScript integration, Vite configuration, and composition.
  Use when building complex animations, styling third-party components, or migrating legacy CSS.

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 CaseCSS ModulesTailwind
Complex animationsBestGood
Third-party component stylingBestHarder
Legacy CSS migrationBestRefactor needed
Rapid prototypingSlowerBest
Design system utilitiesNot idealBest
Component encapsulationBestN/A
Team with CSS expertiseBestEither

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


Documentation Index

Vite Integration

TopicURLDescription
CSS Modules in Vitehttps://vite.dev/guide/features#css-modulesVite's built-in support
Lightning CSShttps://vite.dev/guide/features#lightning-cssFast CSS transforms
PostCSShttps://vite.dev/guide/features#postcssPostCSS configuration

Lightning CSS

TopicURLDescription
Documentationhttps://lightningcss.dev/docs.htmlOfficial docs
CSS Moduleshttps://lightningcss.dev/css-modules.htmlModule support
Transpilationhttps://lightningcss.dev/transpilation.htmlBrowser targeting
Bundlinghttps://lightningcss.dev/bundling.htmlCSS bundling

CSS Modules Spec

TypeScript Integration

TopicURLDescription
typed-css-moduleshttps://github.com/Quramy/typed-css-modulesGenerate .d.ts files
vite-plugin-css-modules-dtshttps://github.com/mrcjkb/vite-plugin-css-modules-dtsVite plugin

CSS Modules Fundamentals

File Naming Convention

code
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

css
/* 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%);
}
tsx
// 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

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

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

Local vs Global Scope

css
/* 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:

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

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

Multiple Compositions

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

Usage in React

tsx
// 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:

bash
npm install lightningcss browserslist
typescript
// 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

PatternExample 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:

javascript
// 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:

typescript
// 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)

typescript
// 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)

bash
npm install -D vite-plugin-css-modules-dts
typescript
// 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:

typescript
// 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

bash
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:

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

Patterns

Component-Scoped Styling

css
/* 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%);
}
tsx
// 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

css
/* 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

css
/* 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:

css
/* 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:

tsx
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

ScenarioApproach
Layout utilities (flex, grid, spacing)Tailwind
Responsive utilitiesTailwind
State variants (hover, focus)Tailwind
Complex animationsCSS Modules
Keyframe animationsCSS Modules
Third-party component overridesCSS Modules
Component state classesCSS Modules
Design system utilitiesTailwind
One-off complex stylesCSS Modules

Performance Benefits

Lightning CSS Advantages

FeatureSpeed Improvement
CSS parsing100x faster than PostCSS
Vendor prefixingBuilt-in, instant
MinificationFaster than cssnano
BundlingParallel 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

typescript
// 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