Linaria uses the tagged template literal syntax for defining styles. This is a new syntax introduced in the recent versions of JavaScript.
A template literal is a string wrapped between backticks (`
) instead of quotes. You can use multi-line strings and string interpolation with them.
const message = `
This is a template literal.
This is an interpolation: ${answer}.
`;
A tagged template literal is a tag attached to a template literal:
const message = someTag`This is a tagged template literal`;
Tags are normal JavaScript function that receive the template literal and it's interpolations, and can perform operations before returning a value.
Linaria exposes 2 tags for styling, css
and styled
. They both support the same set of features, except prop based styles, which is only supported by the styled
tag.
These tags let you write styles in CSS syntax inside the template literals.
The css
tag allows you to create simple class names:
import { css } from '@brandonkal/linaria';
// Create a class name
const title = css`
font-size: 24px;
font-weight: bold;
`;
function Heading() {
// Pass it to a component
return <h1 className={title}>This is a title</h1>;
}
Here the value of the title
variable will be a unique class name which you can pass in the className
prop. This code is equivalent to the following CSS and JavaScript.
CSS:
.title {
font-size: 24px;
font-weight: bold;
}
JavaScript:
function Heading() {
return <h1 className="title">This is a title</h1>;
}
The styled
tag allows you to create a React component with some styles already applied.
You write styled
followed by the name of the HTML tag you want to use, e.g. styled.div
, styled.a
, styled.button
, styled.h3
etc.
import { styled } from '@brandonkal/linaria/react';
// Create a styled component
const Title = styled.h1`
font-size: 24px;
font-weight: bold;
`;
function Heading() {
// Use the styled component
return <Title>This is a title</Title>;
}
This reduces quite a bit of boilerplate code you had to write when you manually created the class name and applied it to a tag. This code is equivalent to the following CSS and JavaScript.
CSS:
.title {
font-size: 24px;
font-weight: bold;
}
JavaScript:
function Title(props) {
return <h1 {...props} className="title" />;
}
function Heading() {
return <Title>This is a title</Title>;
}
Apart from the reduced boilerplate code, this also makes it easier to do prop based styling which you'll cover later in this guide.
You can nest selectors, pseudo-elements and pseudo-selectors similar to Sass, thanks to postCSS.
For example, to change the color of a button on hover, you'll write something like this:
const Button = styled.button`
color: black;
&:hover {
color: blue;
}
`;
The value of &
refers to the class name. This code is equivalent to the following CSS:
.button {
color: black;
}
.button:hover {
color: blue;
}
You can nest pseudo-elements and other selectors:
const Thing = styled.div`
color: black;
&::after {
/* .thing::after */
content: '🌟';
}
h3 {
/* .thing h3 */
color: tomato;
}
.code {
/* .thing .code */
color: #555;
}
& + & {
/* .thing + .thing */
background: yellow;
}
&.bordered {
/* .thing.bordered */
border: 1px solid black;
}
.parent & {
/* .parent .thing */
color: blue;
}
`;
You can also nest media queries:
const Thing = styled.div`
color: black;
@media (min-width: 200px) {
color: blue;
}
`;
This is equivalent to the following CSS:
.thing {
color: black;
}
@media (min-width: 200px) {
.thing {
color: blue;
}
}
You can write keyframe declarations right inside the template literal:
const Box = styled.div`
height: 200px;
width: 200px;
background-color: tomato;
animation: spin 2s linear infinite;
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`;
Here spin
will be replaced with a unique string to scope the animation to the class name.
Like normal template literals, you can interpolate other expressions inside a template literal tagged with css
or styled
.
If you try to interpolate an invalid value, you'll get a build-time error.
You can interpolate variables declared in the same file and imported variables:
const fontSize = 16;
const Title = styled.h1`
font-size: ${fontSize}px;
`;
Here, the fontSize
variable will be evaluated at build-time and inserted into the generated CSS.
You can also call functions while interpolating:
const Button = styled.button`
background-color: ${colors.primary};
&:hover {
background-color: ${darken(0.2, colors.primary)};
}
`;
This concept of JavaScript-in-CSS is immensely powerful. Because darken()
is evaluated at build-time, dead-code-elimination can completely remove the darken utility from the final bundle, meaning you ship less code to your users.
You can also interpolate object styles. Objects are converted to a CSS string before inserting into the style sheet:
const cover = {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
opacity: 1,
minHeight: 360,
'@media (min-width: 200px)': {
minHeight: 480,
},
};
const Title = styled.h1`
font-size: 24px;
${cover};
`;
This is equivalent to the following CSS:
const Title = styled.h1`
font-size: 24px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 1;
min-height: 360px;
'@media (min-width: 200px)': {
minheight: 480px;
}
`;
The interpolated object can have nested selectors, media queries, pseudo-selectors and pseudo-elements. Numeric values which require a unit (e.g. minHeight
in the above example) will be appended with px
unless specified.
When writing React components using the styled
syntax, you can write styles based on the component's props. These values will use CSS variables under the hood and can change dynamically in runtime.
For dynamic prop based styles, pass a function to the interpolation:
const Title = styled.h1`
color: ${props => (props.primary ? 'tomato' : 'black')};
`;
The function will be called with the component's props as an argument when it is rendered. Here, the color of the title will be automatically updated whenever the value of the primary
prop changes.
You can interpolate a styled component or a class name to refer to it. For example:
const title = css`
font-size: 24px;
color: black;
`;
const Paragraph = styled.p`
font-size: 16px;
color: #555;
`;
const Article = styled.article`
/* when referring to class names, prepend a dot (.) */
.${title} {
font-size: 36px;
}
/* when referring to a component, interpolate it as a selector */
${Paragraph} {
font-size: 14px;
margin: 16px;
}
`;
If you want to override a few styles on a styled component you defined earlier, you can do so by wrapping it in the styled(..)
tag:
const Button = styled.button`
font-size: 14px;
background-color: tomato;
padding: 8px;
box-shadow: 0 0.5px 0.3px rgba(0, 0, 0, 0.1);
`;
const LargeButton = styled(Button)`
font-size: 18px;
padding: 16px;
`;
Here, when you use LargeButton
, it'll have all the same styles as Button
except the font-size
and padding
which we overrode. When evaluate === true (the default option), a more specific selector will be generated for the wrapping component's styles.
You can use the styled(..)
tag to style any component as long as they accept a className
and a style
prop:
function CoolComponent({ className, style, ...rest }) {
return (
<div className={className} style={style}>
{...}
</div>
);
}
const StyledCoolComponent = styled(CoolComponent)`
background-color: tomato;
`;
The style
prop is necessary to apply CSS variables. If you're using a component library that doesn't support passing a style
prop, you can wrap it in a div
(or any other tag that makes sense) to apply it:
import { Card } from 'some-library';
function CustomCard({ className, style, ...rest }) {
return (
<div style={style}>
<Card className={className} {...rest} />
</div>
);
}
const StyledCustomCard = styled(CustomCard)`
margin: 16px;
height: ${props => props.height}px;
`;
Normally, the styles are scoped to specific components. But sometimes you may need to write some global styles, for example, to normalize browser inconsistencies, define a font-family etc.
You can do the following to generate unscoped global styles:
// file: globalStyles.js
import { injectGlobal } from '@brandonkal/linaria';
injectGlobal`
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
@font-face {
font-family: 'MaterialIcons';
src: url(../assets/fonts/MaterialIcons.ttf) format('truetype');
}
`;
Using the injectGlobal function will have the side effect of including those styles in the CSS. The example above can be imported as you would import any other CSS file: import './globalStyles.js'
.
It's not possible to use dynamic prop based styles inside global styles but static values can still be interpolated.
Additionally, using a PostCSS plugin is a simple way to bail out of the automatic scoped styles:
For example, you may have a script that applies a tabbing
class to the body if the user is navigating by keyboard.
const Button = styled.button`
:global .tabbing &${[p => p.showOutlines]} {
outline: 4px orange solid;
}
`;