Migrating to Linaria
Linaria is a zero-runtime CSS-in-JS library with an API similar to Emotion, but with significantly improved performance.
The documentation for Linaria can be found on Github, and is worth reading through. We’ll address the differences with Emotion 9 and the migration steps below.
Why Linaria?
In our testing, switching from Emotion to Linaria improves component render time by ~25-30%. This is because Linaria is a zero-runtime CSS-in-JS solution. Whilst Emotion applies styles at runtime by injecting them into style tags in the DOM, Linaria uses Babel to parse the React tree at build time, extracting styles to CSS files.
By extracting styles to CSS files, we can better rely on the browser’s inherent ability to cache CSS, and to load CSS in parallel with JavaScript. Because no JavaScript has to be executed to apply styles, this also reduces the time it takes to render React components.
Though Linaria’s API is very similar to Emotion’s, the use of Linaria requires a slightly different approach to writing styles due to the intentional lack of runtime evaluation.
emotion-to-linaria codemod
We have created a codemod that can be run to convert most components in your stores from Emotion to Linaria. This codemod will update the import declarations and rewrite some of your styling to syntax that is compatible with Linaria.
There are some patterns that are not possible to codemod and will require your input. The codemod will show warnings for those lines of code that need to be manually changed.
Codemod is not fully compatible with Windows, it causes a lot of incorrect modifications. Run Codemod on a MacOS or Linux operating system.
Running the codemod
To run the codemod, simply open your terminal and run this command FROM THE PROJECT ROOT:
npx @jetshop/flight-codemod emotion-to-linaria ./src
Running this command from a different directory may seem like it works, but it will insert the wrong import paths.
Warnings
You may receive warnings for lines of code that could not be automatically modded. Take note of the filename and line number that appear after the warning, as that will indicate where you need to look.
- ** using css
inside a styled template literal is no longer supported. Try using a regular className or consider inline styles **
This occurs when css
has been used inside a styled
or css
template literal. For example:
// This is no longer valid
const CategoryName = styled('span')`
cursor: pointer;
${({ hasSubcategories }) =>
hasSubcategories &&
css`
&:before {
margin-right: 0.5rem;
font-weight: 400;
}
`};
`
function Comp(props) {
return <CategoryName hasSubcategories={props.hasSubcategories} />
}
In this case, the best solution is to use a class name instead:
// This resolves the issue
import { cx } from 'linaria'
const CategoryName = styled('span')`
cursor: pointer;
&.has-subcategories {
&:before {
margin-right: 0.5rem;
font-weight: 400;
}
}
`
function Comp(props) {
return (
<CategoryName
className={cx(props.hasSubcategories && 'has-subcategories')}
/>
)
}
- ** A CSS template literal with an interpolated expression was used inside a className prop. This cannot be polyfilled. Please fix manually - consider using a style
prop instead. **
This error occurs in cases where you have used the css
function inside a className
prop, and have an interpolated expression inside that css
function. For example:
// This is no longer valid
<ColorSwatch
className={css`
border-color: ${swatchColor === 'white' ? '#b8b9b8' : swatchColor};
`}
/>
The best solution here is to use a style
prop:
// This resolves the issue
<ColorSwatch
style={{
borderColor: ${swatchColor === 'white'
? '#b8b9b8'
: swatchColor};
}}
/>
Manual changes
There are some differences from Emotion that the codemod does not handle therefore you have to change them manually.
- ** innerRef
prop name change **
In Emotion to pass a ref
to a styled component you had to use innerRef
prop name. In Linaria the prop name is simply ref
.
- ** withComponent
is not supported **
Linaria does not have withComponent
therefore you have to implement some workarounds to achive the same result.
Let's take the following example:
import Button from '../../ui/Button'
const CheckoutButton = styled(Button)`
background-color: #000000;
`.withComponent('a')
We want to have to use the Button
styling but render it as an a
link component. All we have to do is separate the styling from the Button
component:
// before
const Button = styled('button')`
background-color: blue;
color: white;
border-radius: 4px;
`
export default Button
// after
export const buttonStyle = `
background-color: blue;
color: white;
border-radius: 4px;
`
const Button = styled('button')`
${buttonStyle};
`
export default Button
Differences to Emotion 9
Linaria styled components and dynamic props
Whilst it is still possible to use the styled
syntax to build components using Linaria, props passed to those components may only be used for CSS values. Under the hood, the props will be converted to CSS custom properties, which are not supported in IE11. If you require IE11 support, see alternative approaches below.
// This syntax is fine, although the height and width will not be applied in IE11.
import { styled } from 'linaria/react'
const Box = styled.div`
background-color: orange;
height: ${(props) => props.size}px;
width: ${(props) => props.size}px;
`
function Comp(props) {
return <Box size={props.size} {...props} />
}
If you require IE11 support, the recommended approach is to simply use inline styling for dynamic values:
// This syntax works in all browsers
import { styled } from 'linaria/react'
const Box = styled.div`
background-color: orange;
`
function Comp(props) {
return (
<Box
size="large"
style={{ height: props.size, width: props.size }}
{...props}
/>
)
}
If you’d like to apply different styles conditionally and the values themselves aren’t dynamic, you can also use CSS classes:
// This syntax works in all browsers
import { styled } from 'linaria/react'
import { cx } from 'linaria'
const Box = styled.div`
background-color: orange;
&.important {
background-color: red;
}
&.notice {
background-color: yellow;
}
`
function Comp(props) {
return (
<Box
{...props}
className={cx(props.notice && 'notice', props.important && 'important')}
/>
)
}
The above could also be expressed using the css
function that Linaria exports, instead of the styled
function. The css
function returns a class name that can be applied to the className
prop:
// This syntax works in all browsers
import { cx, css } from 'linaria'
const boxStyles = css`
background-color: orange;
&.important {
background-color: red;
}
&.notice {
background-color: yellow;
}
`
function Comp(props) {
return (
<div
{...props}
className={cx(
boxStyles,
props.notice && 'notice',
props.important && 'important'
)}
/>
)
}
The css
function
As with Emotion 9, Linaria exports a css
function that can be used to create styles that should be passed to an element’s className
prop.
import { css } from 'linaria'
const styles = css`
color: blue;
&:hover {
color: red;
}
`
<div className={styles} />
The difference with Emotion 9 is that styles created with the css
function cannot be interpolated into other style declarations.
// This will not work as intended
import { css } from 'linaria'
const baseStyles = css`
color: blue;
`
const combinedStyles = css`
${baseStyles};
background: white;
`
To achieve this, you should simply omit the css
function from the styles that you want to interpolate:
// This works!
import { css } from 'linaria'
// Do not use `css` here
const baseStyles = `
color: blue;
`
const combinedStyles = css`
${baseStyles};
background: white;
`
The css
prop
Emotion 9 has a css
prop that can be use similarly to style
.
// This no longer works
function Component() {
return (
<div
css={`
color: red;
`}
/>
)
}
Instead of doing this, use a style
prop or move the styles outside of the component
// This is fine
function Component() {
return <div style={{ color: 'red' }} />
}
// or this
const styles = css`
color: red;
`
function Component() {
return <div className={styles} />
}