JavaScript and React course Β· Module 14: Advanced Styling

Comparing Styling Approaches - Galaxy Map

4 min read
In this lesson7

We now have three powerful tools for styling in our arsenal. It's time to compare them and understand when to use each one. It's like choosing a spaceship -- each has its strengths and best use cases.

Comparison table

FeatureCSS ModulesStyled-componentsTailwind CSS
IsolationAutomatic (unique classes)Automatic (unique classes)None (global classes)
Dynamic stylesLimited (CSS variables)Full (props + JS)clsx/cn + conditions
Bundle sizeSmallLarger (runtime JS)Small (purge)
Learning curveLowMediumMedium
ThemingCSS variablesThemeProvidertailwind.config
Pseudo-classesYes (plain CSS)Yes (& syntax)Yes (prefixes)
ResponsivenessMedia queriesMedia queriesPrefixes (md:, lg:)
SSRSeamlessRequires configurationSeamless

When to use CSS Modules?

CSS Modules are ideal when:

  • You want a simple solution without additional libraries
  • The team knows plain CSS well
  • The project uses Create React App or Next.js (built-in support)
  • Performance matters (zero runtime JS)
  • You're migrating from an existing CSS project
1// CSS Modules -- simple, familiar CSS
2import styles from './Card.module.css';
3
4function Card({ title }) {
5  return <div className={styles.card}><h2>{title}</h2></div>;
6}

When to use Styled-components?

Styled-components are ideal when:

  • You need very dynamic styles (dependent on props, state)
  • You're building a component library with built-in styles
  • You want advanced theming with theme switching
  • The project is component-based (styles = part of the component)
1// Styled-components -- styles as part of the component
2const Card = styled.div`
3  background: ${({ variant }) => variant === 'dark' ? '#0a0a2e' : '#ffffff'};
4  padding: ${({ theme }) => theme.spacing.lg};
5`;

When to use Tailwind CSS?

Tailwind CSS is ideal when:

  • You want to quickly prototype interfaces
  • You value design consistency (design system in config)
  • The team likes the utility-first approach
  • You care about a small bundle (purging unused classes)
  • You use Next.js or Vite (great integration)
1// Tailwind -- rapid prototyping
2function Card({ title }) {
3  return (
4    <div className="bg-slate-900 p-6 rounded-xl border border-indigo-700">
5      <h2 className="text-xl font-bold text-purple-400">{title}</h2>
6    </div>
7  );
8}

Mixing approaches

In practice, many projects combine different approaches:

  • Tailwind for layout and rapid prototyping
  • CSS Modules for complex animations and specific components
  • Styled-components for components with dynamic styles

This is perfectly acceptable! The key is to establish conventions within the team and stick to them.

According to React developer surveys:

  1. Tailwind CSS -- growing popularity, dominant in new projects
  2. CSS Modules -- stable popularity, often used in Next.js
  3. Styled-components -- still popular, but slightly declining
  4. Zero-runtime CSS-in-JS (vanilla-extract, Panda CSS) -- new trend, growing interest

Practical tips for choosing

Before choosing a styling approach, ask yourself a few questions:

  1. What is the team size? -- a large team may prefer Tailwind (consistent values) or CSS Modules (familiar CSS)
  2. Do you need dynamic styles? -- if so, styled-components or Emotion will be best
  3. How important is performance? -- for time-critical applications, consider CSS Modules or zero-runtime CSS-in-JS
  4. Are you using SSR? -- CSS Modules and Tailwind work seamlessly, styled-components require additional configuration
  5. Do you have existing CSS? -- if so, CSS Modules offer the easiest migration

There is no single "best" solution -- each approach is a different spaceship, designed for a different type of mission. The key is understanding the trade-offs and choosing what best fits your project and team.

Code for this lesson: App.jsx
1import React, { useState } from 'react';
2import styled from 'styled-components';
3
4const Container = styled.div`
5  font-family: 'Segoe UI', sans-serif;
6  padding: 20px;
7  background: #0a0a2e;
8  color: #e0e0e0;
9  min-height: 100vh;
10`;
11
12const Heading = styled.h1`
13  color: #64b5f6;
14  text-align: center;
15  margin-bottom: 24px;
16`;
17
18const ComparisonGrid = styled.div`
19  display: grid;
20  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
21  gap: 16px;
22  margin-bottom: 24px;
23`;
24
25const ApproachCard = styled.div`
26  background: ${props => props.active ? '#1a1a5e' : '#1a1a4e'};
27  border: 2px solid ${props => props.active ? '#7c4dff' : '#3949ab'};
28  border-radius: 12px;
29  padding: 20px;
30  cursor: pointer;
31  transition: all 0.3s ease;
32  &:hover { border-color: #7c4dff; transform: translateY(-2px); }
33`;
34
35const ApproachName = styled.h3`
36  color: #7c4dff;
37  margin-bottom: 8px;
38  font-size: 18px;
39`;
40
41const ProsList = styled.ul`
42  list-style: none;
43  padding: 0;
44  margin: 8px 0;
45  li {
46    padding: 4px 0;
47    font-size: 14px;
48    &::before { content: '√ '; color: #00e676; }
49  }
50`;
51
52const ConsList = styled.ul`
53  list-style: none;
54  padding: 0;
55  margin: 8px 0;
56  li {
57    padding: 4px 0;
58    font-size: 14px;
59    &::before { content: 'Γ— '; color: #f44336; }
60  }
61`;
62
63const Label = styled.span`
64  font-size: 12px;
65  font-weight: bold;
66  color: ${props => props.type === 'pro' ? '#00e676' : '#f44336'};
67  text-transform: uppercase;
68`;
69
70const approaches = [
71  {
72    name: 'CSS Modules',
73    desc: 'Isolated styles with automatically unique classes',
74    pros: ['Zero runtime JS', 'Plain CSS', 'Built into CRA/Next.js', 'Easy migration'],
75    cons: ['Limited dynamic styles', 'Harder to combine classes', 'No native theming'],
76    bestFor: 'Simple projects, migrating from CSS, Next.js apps',
77  },
78  {
79    name: 'Styled-components',
80    desc: 'CSS-in-JS with tagged template literals and props',
81    pros: ['Dynamic styles (props)', 'ThemeProvider', 'Full pseudo-classes', 'Component extension'],
82    cons: ['Runtime JS', 'Larger bundle', 'SSR requires configuration', 'Learning curve'],
83    bestFor: 'Component libraries, dynamic UI, theming',
84  },
85  {
86    name: 'Tailwind CSS',
87    desc: 'Utility-first framework with classes directly in JSX',
88    pros: ['Fast prototyping', 'Small bundle (purge)', 'Consistent design system', 'Responsiveness (prefixes)'],
89    cons: ['Longer classNames', 'Class learning curve', 'Less readable JSX', 'Configuration required'],
90    bestFor: 'Fast development, prototypes, projects with a design system',
91  },
92];
93
94export default function App() {
95  const [selected, setSelected] = useState(0);
96  const approach = approaches[selected];
97
98  return (
99    <Container>
100      <Heading>Comparing styling approaches</Heading>
101
102      <ComparisonGrid>
103        {approaches.map((a, i) => (
104          <ApproachCard
105            key={a.name}
106            active={i === selected}
107            onClick={() => setSelected(i)}
108          >
109            <ApproachName>{a.name}</ApproachName>
110            <p style={{ fontSize: '14px', color: '#b0bec5' }}>{a.desc}</p>
111          </ApproachCard>
112        ))}
113      </ComparisonGrid>
114
115      <div style={{ background: '#1a1a4e', borderRadius: '12px', padding: '24px', border: '1px solid #3949ab' }}>
116        <h2 style={{ color: '#7c4dff', marginBottom: '16px' }}>{approach.name}</h2>
117
118        <Label type="pro">Pros</Label>
119        <ProsList>{approach.pros.map(p => <li key={p}>{p}</li>)}</ProsList>
120
121        <Label type="con">Cons</Label>
122        <ConsList>{approach.cons.map(c => <li key={c}>{c}</li>)}</ConsList>
123
124        <div style={{ marginTop: '16px', padding: '12px', background: '#0d1137', borderRadius: '8px' }}>
125          <span style={{ color: '#64b5f6', fontWeight: 'bold' }}>Best for: </span>
126          <span style={{ color: '#e0e0e0' }}>{approach.bestFor}</span>
127        </div>
128      </div>
129    </Container>
130  );
131}

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Which approach does NOT require additional runtime JavaScript in the browser?

  2. 2. Which approach is best for rapid interface prototyping?

Hands-on tasks in the game

  • Click in order

    Arrange the composes syntax in CSS Modules:

  • Horizontal ordering

    Arrange the syntax for combining CSS Modules classes with a condition using a template literal:

  • Code editor

    Implement a switcher for three space-themed themes

Useful articles