iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

CSS Modules

CSS Modules give you locally scoped class names without runtime cost - the file system becomes your namespace and the build inlines hashes.

React - CSS Modules

EXAMPLE
// Button.module.css
.button {
  background: #2563eb;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 6px;
  border: none;
  cursor: pointer;
}

.button:hover { background: #1d4ed8; }
.button:disabled { opacity: 0.5; cursor: not-allowed; }

.primary { background: #16a34a; }
.primary:hover { background: #15803d; }


// Button.tsx
import styles from './Button.module.css';
import clsx from 'clsx';

type Props = {
  variant?: 'default' | 'primary';
  disabled?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
};

export function Button({ variant = 'default', disabled, children, onClick }: Props) {
  return (
    <button
      className={clsx(styles.button, variant === 'primary' && styles.primary)}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}


// Usage
import { Button } from './Button';

<Button>Cancel</Button>
<Button variant='primary'>Save</Button>


// vite.config.ts - already supported in Vite
// next.config.js - already supported in Next
// Optional: customise hash format
export default {
  css: {
    modules: {
      generateScopedName: '[name]_[local]__[hash:base64:5]',
    },
  },
};

Why it matters

CSS Modules sit between plain CSS and CSS-in-JS. Zero runtime, no class-name collisions, and the styles ship as a normal stylesheet - which means CDN caching and no FOUC. Reach for them when Tailwind feels too coupled to markup and Emotion feels too heavy.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import s from './Button.module.css';
return <button className={s.primary}>Save</button>;
Try it Yourself »

Discussion

Loading…