Migrate from Emotion
To move a codebase from Emotion to motif, replace the css prop with style props on Box, port
@emotion/styled calls to styled, and swap the theme wiring. The object-style css you
already write maps almost directly.
What maps directly
| Emotion | motif |
|---|---|
<div css={{ color: 'red' }} /> | <Box color="red" /> |
styled.div({ color: 'red' }) | styled('div', { base: { color: 'red' } }) |
styled(Component)({ ... }) | styled(Component, { base: ... }) |
(props) => props.theme.colors.text | '$colors.text.default' |
<ThemeProvider theme={theme}> | <ThemeProvider themes={[theme]} active="light"> |
useTheme() | useTheme() (token tree) / useThemeName() |
&:hover { ... } | _hover={{ ... }} |
@media (min-width: 768px) { ... } | <Box p={{ base: 2, md: 4 }} /> |
css composition (css([a, b])) | object spread into base or a variant |
The css prop and motif's style props solve the same problem — co-located styles without a named
component. The difference is that motif's props are individually typed against the token scales,
where Emotion's css object is a freeform CSSObject.
Mismatches and gaps
- The
cssprop on arbitrary elements. Emotion's JSX pragma addscssto every host element. motif has no pragma — style props live on motif primitives (Box,Stack,Text, …). Port a<div css={…}>to<Box …>; for a one-off host element with no motif primitive, keep a smallstyled('div', …). - Template-literal styles. Emotion accepts CSS strings; motif takes typed objects only. A
csstemplate literal becomes a plain style object. There is no string parser in the resolver. GlobalandinjectGlobal. Emotion ships global-style helpers; motif does not. Use a plain<style>tag or your framework's head manager. See Print styles for the pattern.- The
cx/ class-composition helpers. motif composes through object merging — spread style bags, or layerstyled()calls — not through class-name concatenation. - Server rendering. Emotion's
@emotion/serverextractCriticaland motif'sSSRStyleCollectorsolve the same problem. See Server-side rendering.
Migrate a component
A typical Emotion component:
Becomes:
The theme callback disappears — props.theme.colors.surface.base becomes the static
'$colors.surface.base' reference, resolved by the runtime with no function in the render path.
The css-prop <span> becomes a <Text> with style props; raw pixel values become token
references where a token exists.
Run them side by side
Emotion and motif both inject CSS into the document and their style buckets do not collide.
Install both, port one component family per pull request, and remove Emotion when nothing imports
@emotion/* any more.
- Install motif and define the theme. Mount
<ThemeProvider>alongside Emotion's. - Port
css-prop call sites to motif primitives. These are the loudest diff and the highest leverage — most Emotion apps lean on thecssprop heavily. - Port
styledcomponents in dependency order. - Remove the JSX pragma and uninstall
@emotion/*oncegrep "@emotion"is clean.