Emotion is a performant and flexible CSS-in-JS library designed for writing CSS styles with JavaScript. It provides a framework-agnostic API for generating class names, animations, global styles, and composing CSS with optimized performance and great developer experience.
npm install emotionimport { css, cx, injectGlobal, keyframes } from "emotion";For CommonJS:
const { css, cx, injectGlobal, keyframes } = require("emotion");For Babel macro (compile-time optimization):
import { css, cx, injectGlobal, keyframes } from "emotion/macro";import { css, cx, keyframes, injectGlobal } from "emotion";
// Generate CSS class name
const buttonStyle = css`
background-color: hotpink;
font-size: 24px;
border-radius: 4px;
padding: 8px 12px;
&:hover {
color: white;
}
`;
// Apply styles to element
const button = document.createElement('button');
button.className = buttonStyle;
button.textContent = 'Click me';
// Compose class names
const activeStyle = css`
font-weight: bold;
border: 2px solid blue;
`;
button.className = cx(buttonStyle, isActive && activeStyle);
// Create animations
const bounce = keyframes`
from, 20%, 53%, 80%, to {
transform: translate3d(0,0,0);
}
40%, 43% {
transform: translate3d(0, -30px, 0);
}
`;
// Inject global styles
injectGlobal`
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: sans-serif;
}
`;Emotion is built around several core concepts:
css function transforms style inputs into unique class namescx function provides intelligent class name merging with emotion-aware precedenceinjectGlobal allows injection of global styles into the document headkeyframes generates unique animation names for CSS animationsThe core functionality for creating CSS class names from various style inputs.
/**
* Generates CSS class names from style inputs
* @param template - Template literal or style arguments
* @param args - Interpolated values for template literals
* @returns Generated CSS class name
*/
function css(template: TemplateStringsArray, ...args: Array<Interpolation>): string;
function css(...args: Array<Interpolation>): string;String Styles (Template Literals):
const redText = css`
color: red;
font-size: 16px;
&:hover {
color: darkred;
}
`;
// With interpolation
const color = 'blue';
const dynamicStyle = css`
color: ${color};
font-size: ${props => props.large ? '24px' : '16px'};
`;Object Styles:
const buttonStyle = css({
backgroundColor: 'hotpink',
fontSize: 24,
borderRadius: 4,
'&:hover': {
color: 'white'
}
});
// With dynamic properties
const conditionalStyle = css({
display: 'flex',
color: isError ? 'red' : 'black',
fontSize: isLarge ? 20 : 14
});Array of Styles:
const combinedStyle = css([
{
backgroundColor: 'hotpink',
fontSize: 24
},
isActive && {
fontWeight: 'bold'
},
`
border-radius: 4px;
&:hover {
opacity: 0.8;
}
`
]);Intelligent composition of class names with emotion-aware precedence handling.
/**
* Composes class names with proper precedence handling
* @param classNames - Class name arguments (strings, objects, arrays)
* @returns Composed class name string
*/
function cx(...classNames: Array<ClassNamesArg>): string;Basic Composition:
const baseStyle = css`
color: black;
font-size: 16px;
`;
const activeStyle = css`
color: blue;
font-weight: bold;
`;
// Emotion-aware composition (activeStyle will override baseStyle color)
const combinedClass = cx(baseStyle, isActive && activeStyle);Conditional Styles:
const buttonClass = cx(
baseButtonStyle,
{
[primaryStyle]: isPrimary,
[dangerStyle]: isDanger,
[disabledStyle]: isDisabled
},
'external-class-name'
);Array Composition:
const composedClass = cx([
baseStyle,
conditionalStyle,
isSpecial && specialStyle
]);Injection of global CSS styles into the document.
/**
* Injects global styles into the document head
* @param template - Template literal or style arguments
* @param args - Interpolated values for template literals
*/
function injectGlobal(template: TemplateStringsArray, ...args: Array<Interpolation>): void;
function injectGlobal(...args: Array<Interpolation>): void;Template Literal Global Styles:
injectGlobal`
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Helvetica Neue', sans-serif;
background-color: #f5f5f5;
}
@font-face {
font-family: 'CustomFont';
src: url('./fonts/custom.woff2') format('woff2');
}
`;Object Global Styles:
injectGlobal({
'*': {
boxSizing: 'border-box'
},
body: {
margin: 0,
fontFamily: 'sans-serif'
}
});Creation of CSS animation keyframes with unique naming.
/**
* Generates unique animation names for CSS keyframes
* @param template - Template literal or keyframe arguments
* @param args - Interpolated values for template literals
* @returns Unique animation name for use in CSS
*/
function keyframes(template: TemplateStringsArray, ...args: Array<Interpolation>): string;
function keyframes(...args: Array<Interpolation>): string;Template Literal Keyframes:
const fadeIn = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
const animatedElement = css`
animation: ${fadeIn} 0.5s ease-in-out;
`;Object Keyframes:
const slideIn = keyframes({
'0%': {
transform: 'translateX(-100%)'
},
'100%': {
transform: 'translateX(0)'
}
});
const slidingElement = css({
animation: `${slideIn} 0.3s ease-out`
});Complex Animations:
const bounce = keyframes`
from, 20%, 53%, 80%, to {
transform: translate3d(0,0,0);
}
40%, 43% {
transform: translate3d(0, -30px, 0);
}
70% {
transform: translate3d(0, -15px, 0);
}
90% {
transform: translate3d(0, -4px, 0);
}
`;Low-level cache and stylesheet management functions.
/**
* Clears all generated styles and resets the cache
*/
function flush(): void;
/**
* Hydrates server-rendered styles on the client
* @param ids - Array of style IDs to mark as server-rendered
*/
function hydrate(ids: Array<string>): void;
/**
* Internal emotion cache instance
*/
const cache: EmotionCache;
/**
* Low-level stylesheet interface
*/
const sheet: StyleSheet;
/**
* Merges class names with registered styles
* @param className - Class name to merge
* @returns Merged class name
*/
function merge(className: string): string;
/**
* Retrieves registered styles for class names
* @param registeredStyles - Array to populate with registered styles
* @param className - Class name to look up
* @returns Raw class name
*/
function getRegisteredStyles(registeredStyles: Array<string>, className: string): string;Server-Side Rendering:
// Server-side: collect style tags
const { html, ids, css } = renderToString(<App />);
// Client-side: hydrate
hydrate(ids);Cache Management:
// Clear all styles (useful for testing)
flush();
// Access low-level sheet
sheet.speedy(false); // Disable fast insertion for debugging/**
* Any valid style interpolation value
*/
type Interpolation = any;
/**
* Valid class name arguments for cx function
*/
type ClassNamesArg =
| undefined
| null
| string
| boolean
| { [className: string]: boolean | null | undefined }
| ArrayClassNamesArg;
/**
* Array of class name arguments
*/
interface ArrayClassNamesArg extends Array<ClassNamesArg> {}
/**
* Emotion cache interface for storing styles and metadata
*/
interface EmotionCache {
registered: { [key: string]: string };
inserted: { [key: string]: boolean };
sheet: StyleSheet;
key: string;
}
/**
* Low-level stylesheet interface
*/
interface StyleSheet {
insert(rule: string): void;
flush(): void;
speedy(value: boolean): void;
tags: Array<HTMLStyleElement>;
isSpeedy: number;
ctr: number;
}
/**
* Object-based style interpolation
*/
interface ObjectInterpolation {
[key: string]: any;
}
/**
* Array-based style interpolation
*/
interface ArrayInterpolation extends Array<Interpolation> {}
/**
* Function-based style interpolation
*/
interface FunctionInterpolation {
(props?: any): Interpolation;
}
/**
* Component selector for styled components
*/
interface ComponentSelector {
__emotion_styles: any;
}Emotion provides a Babel macro for compile-time optimizations:
// Import from macro for build-time optimizations
import { css, cx, keyframes, injectGlobal } from "emotion/macro";
// Same API, but with compile-time CSS extraction and optimization
const optimizedStyle = css`
color: red;
font-size: 16px;
`;Benefits of Babel Macro:
Emotion gracefully handles various edge cases: