Intermediate

Styling Apps in React 18

Compare React styling techniques — CSS-in-JS (styled-jsx) and CSS Modules with best practices.

Demo project: Electro Sleuth application (newsletter signup) — Next.js + React


Table of Contents

  1. Course Overview
  2. Making Styling Decisions
  3. Comparing React Styling Techniques
  4. Coding with CSS-in-JS (styled-jsx)
  5. Coding with CSS Modules
  6. Recap and Best Practices

1. Course Overview

This course covers different styling approaches in React 18:

  • Decide how to configure your styling system
  • Compare different styling techniques
  • Use these techniques in a real React project

2. Making Styling Decisions

Why Style an Application

GoalDescription
ProfessionalismBuilds user trust
UsabilityFacilitates navigation and understanding
AttractivenessCreates a good impression and retains users

Decision Criteria

CriteriaQuestions to Ask
TeamExperience level, preferences, team size
ProductRequired features, UI complexity
BrowsersCompatibility constraints
ToolsBuild ecosystem, bundler, framework
DeliverySPA, SSR, islands of interactivity
LifecycleLong-term, maintainability, evolution

Key advice: Don’t fall into analysis paralysis. All options are valid. Choosing and moving forward is better than seeking perfection.


3. Comparing React Styling Techniques

Fundamental CSS Challenges

In 2014, Christopher Chedeau (Facebook) identified structural CSS challenges:

#ChallengeDescription
1Global namespaceAll selectors are global by default
2DependenciesDifficult to trace which styles apply where
3Dead code eliminationCan’t know if a selector is still used
4MinificationLong class names can’t be automatically shortened
5Sharing constantsHard to share values between CSS and JS
6Non-deterministic resolutionStyle order can vary by bundler
7IsolationOne component can affect another’s styles

Inline Styles

function MyComponent() {
  const style = {
    color: 'crimson',
    fontSize: '16px',        // camelCase, not font-size
    backgroundColor: '#fff',
  };

  return <div style={style}>Hello</div>;
}

// Inline direct (double braces: one for JSX, one for object)
function MyComponent() {
  return <div style={{ color: 'crimson', fontSize: 16 }}>Hello</div>;
}
AdvantageDisadvantage
No library requiredNo CSS pseudo-classes
Total co-locationNo media queries
Full isolationNo @keyframes animations
Easy dynamic stylesMaximum specificity

CSS-in-JS

Popular libraries: styled-components, styled-jsx (Next.js), emotion, Linaria, vanilla-extract

Example with styled-jsx (Next.js):

function Container({ children }) {
  return (
    <section>
      {children}
      <style jsx>{`
        section {
          background: #2b283d;
          padding: 1em;
          max-width: 100%;
        }
        @media (min-width: 800px) {
          section {
            font-size: 2.25em;
            max-width: 700px;
          }
        }
      `}</style>
    </section>
  );
}
AdvantageDisadvantage
Full CSS (pseudo-classes, media queries, animations)Requires third-party library
Automatic isolationRuntime overhead (for some libraries)
Dynamic styles via JS propsLearning curve
Total co-locationMore CSS in JS bundle

CSS Stylesheets

/* MyComponent.css */
.container { background: #2b283d; padding: 1em; }
.button { background: white; color: #070222; font-weight: bold; }
import './MyComponent.css';

function MyComponent() {
  return (
    <div className="container">
      <button className="button">Click me</button>
    </div>
  );
}
AdvantageDisadvantage
Familiar, native CSS syntaxGlobal namespace (naming collisions)
No special build stepHard to isolate styles by component
Optimal browser cachingDead code hard to detect

CSS Modules

Author file:   .button { ... }
After compile: .button__abc123 { ... }
/* Newsletter.module.css */
.container { background: #2b283d; padding: 1em; }
.button { background: white; color: #070222; }
.buttonActive {
  composes: button;
  border-bottom: 3px solid var(--accent);
}
import css from './Newsletter.module.css';

function Newsletter() {
  const [active, setActive] = React.useState(false);
  return (
    <section className={css.container}>
      <button className={active ? css.buttonActive : css.button}>
        Sign up
      </button>
    </section>
  );
}
AdvantageDisadvantage
Automatic isolation via hashingRequires module bundler
Native CSS syntaxSharing between CSS files still difficult
Dead code elimination possibleNo native JS dynamic styles
composes for reuseGenerated class names in DOM

Comparison Table

CriteriaInline StylesCSS-in-JSCSS StylesheetsCSS Modules
Pseudo-classes (:hover)NoYesYesYes
Media queriesNoYesYesYes
@keyframes animationsNoYesYesYes
Global isolationYesYesNoYes
Co-locationYesYesPartialPartial
JS dynamic stylesYesYesNoLimited
JS/CSS constant sharingYesYesNoNo
Build step requiredNoNoNoYes
Third-party libraryNoYesNoNo
Performance (cache)NoVariesYesYes

4. Coding with CSS-in-JS (styled-jsx)

Project Structure

src/
├── pages/
│   └── index.js
└── components/
    ├── index.css          ← global styles (reset, body)
    └── Newsletter.js      ← main component with styled-jsx

Global styles (index.css):

* { box-sizing: border-box; }
body {
  margin: 0;
  background: #070222;
  color: #fff;
  font-size: 18px;
}

Container Component with Media Query

function Container(props) {
  return (
    <section>
      {props.children}
      <style jsx>{`
        section {
          position: relative;
          max-width: 100%;
          font-size: 1.25em;
          padding: 1em 1em 2em 1em;
          background: #2b283d;
        }
        @media (min-width: 800px) {
          section {
            font-size: 2.25em;
            max-width: 700px;
          }
        }
      `}</style>
    </section>
  );
}

Email Component with Focus State

function EmailInput(props) {
  return (
    <>
      <input {...props} />
      <style jsx>{`
        input {
          height: 2em;
          font-size: 0.85em;
          padding: 0 0.5em;
          width: 100%;
          border: 1px solid black;
        }
        input:focus {
          outline: 2px solid #fff;
          outline-offset: 0.15em;
        }
      `}</style>
    </>
  );
}

Submit Component with Dynamic Styles

const color = {
  spectrum1: '#ff598a',
  spectrum2: '#de56e8',
  spectrum3: '#b36bff',
  spectrum4: '#5b56e8',
  spectrum5: '#5e9fff',
};

function Submit(props) {
  return (
    <button>
      {props.children}
      <style jsx>{`
        button {
          background: #fff;
          color: #070222;
          font-weight: bold;
          cursor: pointer;
          transition: all 300ms;

          /* Conditional styles based on props.active */
          height: ${props.active ? 'auto' : '0'};
          width: ${props.active ? 'auto' : '0'};
          font-size: ${props.active ? '1em' : '0'};
          border-bottom: ${props.active
            ? `3px solid ${color.spectrum5}`
            : '0'};
        }
        ${props.active ? `
          button:hover {
            border-bottom-color: ${color.spectrum1};
            scale: 1.2;
          }
        ` : ''}
      `}</style>
    </button>
  );
}

Key point: JavaScript template literals (${expression}) allow injecting prop values directly into CSS — something impossible with traditional stylesheets.

Bar Component with Keyframes Animation

function Bar(props) {
  return (
    <div>
      <style jsx>{`
        div {
          height: ${props.active ? '100%' : '0.5em'};
          animation: jitter 350ms ease-out infinite alternate;
          width: 20%;
          transform-origin: bottom;
          transition: all 1s;
        }
        div:nth-child(1n) { background: ${color.spectrum1}; animation-delay: 0; }
        div:nth-child(2n) { background: ${color.spectrum2}; animation-delay: 50ms; }
        div:nth-child(3n) { background: ${color.spectrum3}; animation-delay: 100ms; }
        @keyframes jitter {
          0% { transform: scaleY(1); }
          100% { transform: scaleY(0.9); }
        }
      `}</style>
    </div>
  );
}

Header with :global Selector

function Header(props) {
  return (
    <header>
      {props.children}
      <style jsx>{`
        header {
          text-transform: uppercase;
          font-size: 0.85em;
        }
        /* :global() targets child elements rendered by React */
        header :global(h2) {
          margin: 0 0 0.5em 0;
        }
      `}</style>
    </header>
  );
}

5. Coding with CSS Modules

Import and Usage

import css from './Newsletter.module.css';

export default function Newsletter() {
  const [email, setEmail] = React.useState('');
  const isActive = email.includes('@') && email.includes('.');

  return (
    <section className={css.container}>
      <input
        type="email"
        className={css.email}
        value={email}
        onChange={evt => setEmail(evt.target.value)}
      />
      <button className={isActive ? css.submitActive : css.submit}>
        Sign up
      </button>
    </section>
  );
}

Newsletter.module.css

.container {
  --accent: #5e9fff;
  --highlight: #ff598a;

  position: relative;
  max-width: 100%;
  font-size: 1.25em;
  padding: 1em 1em 2em 1em;
  background: #2b283d;
}

@media (min-width: 800px) {
  .container { font-size: 2.25em; max-width: 700px; }
}

.email {
  height: 2em;
  font-size: 0.85em;
  padding: 0 0.5em;
  width: 100%;
  border: 1px solid black;
}
.email:focus {
  outline: 2px solid #fff;
  outline-offset: 0.15em;
}

.submit {
  position: absolute;
  bottom: 0;
  background: #fff;
  color: #070222;
  font-weight: bold;
  cursor: pointer;
  transition: all 300ms;
  height: 0;
  width: 0;
  font-size: 0;
}

/* Active state — composition from .submit */
.submitActive {
  composes: submit;
  height: auto;
  width: auto;
  font-size: 1em;
  padding: 0.25em 1em;
  border-bottom: 3px solid var(--accent);
}
.submitActive:hover {
  border-bottom-color: var(--highlight);
  scale: 1.2;
}

Class Composition with composes

.button {
  padding: 0.5em 1em;
  border: none;
  cursor: pointer;
}

/* Variant — inherits from .button and adds/overrides rules */
.buttonPrimary {
  composes: button;
  background: #ff598a;
  color: white;
  font-weight: bold;
}

/* Composition from another file */
.buttonDanger {
  composes: button from './shared.module.css';
  background: #c1121f;
}

CSS Custom Properties in Modules

/* Design tokens at root component level */
.container {
  --color-primary: #ff598a;
  --color-secondary: #5e9fff;
  --spacing-sm: 0.5em;
  --spacing-md: 1em;
}

/* Consumption in child classes */
.title {
  color: var(--color-primary);
  margin-bottom: var(--spacing-sm);
}

6. Recap and Best Practices

Decision Flowchart

flowchart TD
    Start([Choose a CSS approach]) --> Q1{Need dynamic styles\nbased on JS state?}

    Q1 -- Yes --> Q2{Prefer staying\nin JS?}
    Q1 -- No --> Q3{Do you have\na bundler configured?}

    Q2 -- Yes --> B[CSS-in-JS\nstyled-components / emotion / styled-jsx]
    Q2 -- No --> A[Inline Styles\nfor simple cases]

    Q3 -- Yes --> D[CSS Modules\nBest balance CSS + isolation]
    Q3 -- No --> C[Traditional CSS Stylesheets]

Sweet Spots Table

ApproachIdeal Use Case
Inline StylesQuick prototypes, fully dynamic styles, completely isolated components
CSS-in-JSApps with complex theming, design systems, highly conditional styles
CSS StylesheetsSimple apps, CSS-familiar teams, critical performance
CSS ModulesMost React apps — best compromise between isolation and CSS familiarity

General Best Practices

  1. Co-locate styles with their components
  2. Use CSS Custom Properties for design tokens (colors, spacing, sizes)
  3. Avoid global namespace — prefer CSS Modules or CSS-in-JS for component styles
  4. Think in components — each component manages its own styles
  5. Use composes instead of copy-pasting in CSS Modules
  6. Dynamic styles are best handled in CSS-in-JS or via class toggling in CSS Modules
  7. Don’t over-engineer — a simple imported .css can suffice for many cases

“In the end, all you need is a class name on an element with selectors to match. How you get there is much less important.”


Search Terms

styling · apps · react · typescript · frontend · development · css · component · coding · css-in-js · decision · styles

Interested in this course?

Contact us to book it or get a custom training plan for your team.