NextjsCSS-in-JS

CSS-in-JS

CSS-in-JS libraries like Emotion and styled-components let you write CSS rules directly inside your JavaScript/TypeScript files, often scoped to a single component and able to react to props or a theme at runtime. It's a popular pattern in the React ecosystem — and, notably, it's exactly how this site's own UI library, MUI, is built.

Emotion's styled API

TSX
'use client'

import styled from '@emotion/styled'

const Button = styled.button<{ variant?: 'primary' | 'secondary' }>`
  padding: 8px 16px;
  border-radius: 6px;
  border: none;
  font-weight: 600;
  cursor: pointer;
  background-color: ${(props) => (props.variant === 'secondary' ? 'transparent' : '#0070f3')};
  color: ${(props) => (props.variant === 'secondary' ? '#0070f3' : 'white')};
`

export default Button
Warning
Runtime CSS-in-JS libraries inject and update <style> tags using JavaScript that runs in the browser, which means any component using them must be a Client Component ('use client'). They also generally need extra, library-specific configuration to work correctly with the App Router's streaming server rendering — a Next.js next.config.mjs compiler option for styled-components, or a dedicated cache provider wrapping the app for Emotion — so that styles generated on the server get flushed into the initial HTML instead of causing a flash of unstyled content.

Emotion cache provider setup (simplified)

TSX
'use client'

import { CacheProvider } from '@emotion/react'
import createCache from '@emotion/cache'
import { useServerInsertedHTML } from 'next/navigation'
import { useState } from 'react'

export default function EmotionProvider({
  children,
}: {
  children: React.ReactNode
}) {
  const [cache] = useState(() => createCache({ key: 'css' }))

  useServerInsertedHTML(() => (
    <style
      data-emotion={`${cache.key} ${Object.keys(cache.inserted).join(' ')}`}
      dangerouslySetInnerHTML={{
        __html: Object.values(cache.inserted).join(' '),
      }}
    />
  ))

  return <CacheProvider value={cache}>{children}</CacheProvider>
}
Themed, dynamic styles

The reason teams reach for CSS-in-JS despite the extra setup is dynamic styling: a component's CSS can depend directly on props or a shared theme object, computed at render time rather than toggled between a fixed set of pre-written classes.

TSX
'use client'

import { css } from '@emotion/react'

function Badge({ status }: { status: 'success' | 'error' }) {
  return (
    <span
      css={css`
        padding: 2px 8px;
        border-radius: 999px;
        background-color: ${status === 'success' ? '#e6f4ea' : '#fdecea'};
        color: ${status === 'success' ? '#1e7e34' : '#c62828'};
      `}
    >
      {status}
    </span>
  )
}
Note
This site is a working example of CSS-in-JS in production: it uses MUI with its Emotion engine underneath for the entire component library, alongside a small amount of Global CSS for resets. The ThemeProvider in src/providers and MUI's built-in App Router cache integration handle the server/client style-flushing concerns described above, so individual tutorial pages don't need to think about it.
Zero-runtime alternatives
Because runtime CSS-in-JS forces components into the client bundle and adds a style-computation cost on every render, a newer generation of tools — vanilla-extract, Panda CSS, and Emotion's and styled-components' own compiler-based modes — compile CSS-in-JS syntax down to static CSS files at build time, keeping the developer experience of colocated, typed styles while avoiding the runtime cost and the Client Component requirement. If you like writing styles in JavaScript but want a Server Component-friendly result, these are worth exploring as a next step beyond this introduction.
Tip
If you're starting a project from scratch and don't already have a component library tied to a specific CSS-in-JS engine, weigh Tailwind CSS or CSS Modules first — they avoid the Client Component and configuration overhead entirely. Reach for CSS-in-JS when a library you depend on (like MUI) already uses it, or when you specifically need styles computed from runtime theme/prop state.
  • CSS-in-JS libraries (Emotion, styled-components) write CSS rules inside JS/TS files, often driven by props or a theme.

  • Runtime CSS-in-JS requires the Client Component directive and App Router-specific setup to flush server-rendered styles correctly.

  • This site itself uses MUI, which is built on Emotion, alongside a small Global CSS layer.

  • Zero-runtime alternatives (vanilla-extract, Panda CSS, compiler modes) compile CSS-in-JS to static CSS at build time, avoiding the Client Component requirement.