CSS-in-JS
Emotion's styled API
'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<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)
'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.
'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>
)
}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
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.