Migrating from Emotion
Migrate your project from Emotion to Panda and see how css, styled, and theming map across.
Emotion computes styles at runtime and ships no variant system, color modes, or layout components, so this
migration is mostly replacing hand-rolled patterns with typed built-ins. Two constraints shape the work:
css`...` template literals become objects, and Emotion's runtime plumbing gets deleted, not translated.
Template literals to objects
Panda's extractor reads style objects from source; it does not parse CSS text. Emotion's idiomatic tagged templates become objects:
import { css } from '@emotion/react'
// before
const style = css`
color: white;
background: blue;
&:hover {
background: darkblue;
}
`import { css } from '../styled-system/css'
// after
const style = css({
color: 'white',
background: 'blue',
_hover: { background: 'darkblue' }
})Emotion's object syntax converts almost unchanged. styled.x tagged templates follow the same rule; the
conversion is identical to Migrating from Styled Components.
css prop
Emotion's css prop needs the @jsxImportSource @emotion/react pragma. Panda's styled factory accepts a css
prop with no pragma or special JSX runtime:
/** @jsxImportSource @emotion/react */
// before
<div css={{ color: 'white', background: 'blue' }} />import { styled } from '../styled-system/jsx'
// after
<styled.div css={{ color: 'white', bg: 'blue' }} />See JSX Style Props.
cx carries over by name, and Panda's resolves style conflicts instead of just joining strings:
// before
import { cx } from '@emotion/css'
// after
import { cx } from '../styled-system/css'Theming
Emotion passes a plain theme object through ThemeProvider and reads it back with useTheme:
// before
const theme = { colors: { brand: '#0ea5e9' } }
<ThemeProvider theme={theme}>...</ThemeProvider>
function Button() {
const theme = useTheme()
return <button css={{ background: theme.colors.brand }} />
}Panda resolves tokens at build time, so the provider and the hook both disappear:
panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
theme: {
extend: {
tokens: {
colors: {
brand: { value: '#0ea5e9' }
}
}
}
}
})import { css } from '../styled-system/css'
<button className={css({ bg: 'brand' })} />Where you read the theme outside a style object, token replaces useTheme:
import { token } from '../styled-system/tokens'
<Chart color={token('colors.brand')} />See Tokens.
Hand-rolled patterns to built-ins
The features teams build on top of Emotion are Panda built-ins:
Variant functions become typed recipes. button({ variant: 'accent' }) is a
type error if accent was never defined; the hand-rolled version has no safety net.
// before: prop branching in a template
const Button = styled.button`
${props => (props.variant === 'primary' ? `background: blue;` : `background: gray;`)}
`// after
import { cva } from '../styled-system/css'
export const button = cva({
variants: {
variant: {
primary: { bg: 'blue.500' },
secondary: { bg: 'gray.500' }
}
}
})Theme-swapping for dark mode becomes a semantic token, defined once, no context read at the usage site:
panda.config.ts
theme: {
extend: {
semanticTokens: {
colors: {
bg: { value: { base: 'white', _dark: 'gray.900' } }
}
}
}
}<div className={css({ bg: 'bg' })} />The Global component becomes the globalCss config key (Global Styles):
panda.config.ts
export default defineConfig({
globalCss: {
body: { margin: 0 }
}
})Hand-built Box/Stack components become patterns:
import { Box, Stack } from '../styled-system/jsx'
<Stack gap="4">
<Box bg="gray.100">Item</Box>
</Stack>The keyframes helper becomes theme.keyframes, referenced by name and
composable via Animation Styles.
SSR and cache teardown
Emotion's server plumbing exists to flush runtime styles into HTML. Panda's CSS is a static file, so delete it all, nothing replaces it:
@emotion/cacheandCacheProvidersetups (including nonce and insertion-point config)extractCriticalToChunks/constructStyleTagsFromChunksserver code@emotion/babel-pluginand the@jsxImportSourcepragmas- The
ThemeProvider, once every consumer reads tokens instead
Run both during the migration
Panda's CSS ships inside cascade layers and Emotion's injected styles are unlayered, so legacy styles win over converted components by default. See Migrating alongside unlayered CSS for the two ways to control which side wins.
After migrating
- Drop the styling runtime. Source transforms rewrite static calls to plain class strings at build time; combined with leaving Emotion, your bundle ships no styling code at all.
- Lint against your theme. The lint rules flag raw values where a token exists, which keeps the token discipline the migration just bought you.
See also
- Migrating from Styled Components for the shared
styledconversion in more depth. - Dynamic Styles for values that genuinely only exist at runtime.