Skip to main content

CSS Styling Methods: From Vanilla to CSS-in-JS

Easy•

Each styling method has strengths. Understanding trade-offs helps choose the right approach.

Quick Decision Guide

Styling Methods Comparison:

Vanilla CSS: Simple, no build step. Best for small projects.

CSS Modules: Scoped styles, no runtime. Great for React/Vue.

Tailwind: Utility classes, rapid development. Popular choice.

CSS-in-JS: Dynamic styles, component-coupled. Adds runtime overhead.

Component Libraries: Pre-built components. Fast but opinionated.

Choose based on: Project size, team preference, performance needs.

Vanilla CSS

Traditional Stylesheets

/* styles.css */
.button {
  background: #007bff;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 4px;
}

.button:hover {
  background: #0056b3;
}
<link rel="stylesheet" href="styles.css">
<button class="button">Click me</button>

Pros & Cons

Pros:

•✅ No build step
•✅ Browser native
•✅ Easy to learn
•✅ Great caching

Cons:

•❌ Global scope (naming conflicts)
•❌ No dead code elimination
•❌ Hard to maintain at scale
•❌ No type safety

Best for: Simple sites, static pages, small projects

CSS Modules

Scoped CSS Files

/* Button.module.css */
.button {
  background: #007bff;
  padding: 0.5rem 1rem;
}

.primary {
  background: #007bff;
}

.secondary {
  background: #6c757d;
}
// Button.jsx
import styles from './Button.module.css';

function Button({ variant = 'primary' }) {
  return (
    <button className={styles[variant]}>
      Click me
    </button>
  );
}

Generated HTML:

<button class="Button_primary__a3xZ">Click me</button>

Pros & Cons

Pros:

•✅ Locally scoped (no conflicts)
•✅ No runtime overhead
•✅ Works with existing CSS
•✅ Dead code elimination

Cons:

•❌ No dynamic styles
•❌ Requires build step
•❌ Verbose for complex styles

Best for: React/Vue apps, team familiar with CSS

Tailwind CSS

Utility-First Framework

function Button({ variant = 'primary' }) {
  const baseClasses = "px-4 py-2 rounded font-semibold";
  const variantClasses = {
    primary: "bg-blue-500 hover:bg-blue-700 text-white",
    secondary: "bg-gray-500 hover:bg-gray-700 text-white"
  };
  
  return (
    <button className={`${baseClasses} ${variantClasses[variant]}`}>
      Click me
    </button>
  );
}

With clsx/cn Utility

import { clsx } from 'clsx';

<button className={clsx(
  'px-4 py-2 rounded',
  variant === 'primary' && 'bg-blue-500',
  variant === 'secondary' && 'bg-gray-500',
  disabled && 'opacity-50 cursor-not-allowed'
)}>
  Click me
</button>

Pros & Cons

Pros:

•✅ Rapid development
•✅ Consistent design system
•✅ Purges unused styles
•✅ Responsive utilities built-in

Cons:

•❌ Verbose className strings
•❌ Learning curve for utilities
•❌ Can be hard to read

Best for: Fast prototyping, teams wanting consistency

CSS-in-JS

Styled-Components

import styled from 'styled-components';

const Button = styled.button`
  background: ${props => props.variant === 'primary' ? '#007bff' : '#6c757d'};
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  
  &:hover {
    opacity: 0.9;
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

// Usage
<Button variant="primary">Click me</Button>
<Button variant="secondary" disabled>Disabled</Button>

Emotion

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

const buttonStyle = css`
  background: #007bff;
  padding: 0.5rem 1rem;
  border-radius: 4px;
`;

<button css={buttonStyle}>Click me</button>

Pros & Cons

Pros:

•✅ Dynamic styles with props
•✅ Scoped styles
•✅ TypeScript support
•✅ Theme support built-in

Cons:

•❌ Runtime overhead
•❌ Larger bundle size
•❌ Server-side rendering complexity

Best for: Apps needing dynamic theming, prop-based styles

Comparison & Decision Matrix

Performance Comparison

Method          | Bundle Size | Runtime Cost | Build Time
----------------|-------------|--------------|------------
Vanilla CSS     | Small       | None         | None
CSS Modules     | Small       | None         | Fast
Tailwind        | Small*      | None         | Fast
CSS-in-JS       | Large       | High         | Slow

* After purging unused styles

Decision Matrix

Choose Vanilla CSS when:

•Small, simple project
•No build pipeline
•Static site

Choose CSS Modules when:

•React/Vue app
•Team knows CSS well
•Want scoping without runtime

Choose Tailwind when:

•Rapid prototyping
•Want consistent design system
•Team can learn utilities

Choose CSS-in-JS when:

•Need dynamic styles (themes)
•Props-based styling
•Don't mind runtime cost

Hybrid Approaches

// Tailwind + CSS Modules for custom styles
import styles from './Button.module.css';

<button className={`${styles.custom} px-4 py-2 bg-blue-500`}>
  Hybrid
</button>

// Tailwind + Styled-components for dynamic needs
import styled from 'styled-components';

const Button = styled.button.attrs({
  className: 'px-4 py-2 rounded'
})`
  background: ${props => props.theme.primary};
`;