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};
`;