Zero-Runtime CSS From JS With Linaria

Linaria is a library which allows you to write CSS inside your JavaScript without the runtime cost.

The CSS is extracted out to plain old CSS files with the Babel preset, and critical CSS can be determined with the included helpers.

The idea for Linaria came from css-literal-loader and the syntax of styled-components. It also uses the same parser as styled-components. Glam by Sunil Pai has been another great source of inspiration.

The API looks like this:

import { css } from 'linaria';
const title = css`
text-transform: uppercase;
font-weight: 300;
&:before {
border-left: 2px solid grey;
}
`;

You write CSS syntax (plus nesting) in a tagged template literal and tag it with the css function from Linaria. It returns a class name for you to use. This gets optimized with the Babel preset which reduces it down to just a class name:

const title = 'title_gdr45ky';

It’s possible to use JavaScript expressions inside the template literal and they will be evaluated at build time, useful for shared utility functions and constants:

import { css } from 'linaria';
import { darken } from 'color';
import colors from './colors';
import media from './media';
const AVATAR_SIZE = 48;
const avatar = css`
background-color: ${darken(colors.placeholder, 10)};
height: ${AVATAR_SIZE}px;
width: ${AVATAR_SIZE}px;
border-radius: ${AVATAR_SIZE / 2}px;
${media('large')} {
border-radius: 2px;
}
`;

You can also compose multiple styles together with the include helper:

import { css, include } from 'linaria';
const heading = css`
font-family: Helvetica, sans-serif;
`;
const title = css`
${include(heading)};
font-weight: 300;
`;

which is equivalent to writing:

import { css } from 'linaria';
const title = css`
font-family: Helvetica, sans-serif;
font-weight: 300;
`;

Features

  • CSS is extracted at build time, no runtime is included
  • JavaScript expressions such as function calls, variables, conditionals etc. are supported and evaluated at build time
  • CSS can be extracted for inlining during SSR
  • CSS syntax with Sass like nesting
  • Integrates with existing tools like Webpack to provide features such as Hot Reload

Why?

Existing CSS in JS libraries are pretty amazing as they provide several benefits over the traditional way of writing CSS, making it much more maintainable. They also come with its costs:

  • CSS needs to be parsed, auto-prefixed and applied to the document at runtime
  • Bundle-size is increased as the CSS parser needs to be bundled with JavaScript bundle
  • CSS code is bundled with JavaScript, making it impossible to download and parse both in parallel
  • Even if you do SSR and serve CSS in parallel, the same CSS is still duplicated in the JavaScript bundled, and wastefully parsed again

These costs may not be significant for all applications, and most of the time, it outweighs the benefits gained from CSS in JS.

The main goal of Linaria is to provide a similar level of flexibility and maintainability while not having the runtime cost associated with it.

How does it differ from CSS modules?

It’s pretty similar, except a few key differences:

  • You can write CSS in the same file as rest of the component, no back n forth between files
  • You can share configuration between your CSS and JavaScript without having to keep multiple files in sync
  • You can just use JavaScript for loops, conditionals etc., along with various JavaScript libraries like polished
  • You can make sure that you don’t have any CSS you don’t use with linters like ESLint, and it also works with tools likes Flow or Jest

How does it differ from other CSS in JS libraries?

Despite the similarities, there are many differences between Linaria and the existing CSS in JS libraries:

  • The CSS is extracted out from the JavaScript completely, and no runtime is left
  • It is required that all the CSS can be evaluated at build time, which means it’s currently not possible to use dynamic values like props
  • The CSS can be served as a separate file, improving load time by parallelizing the downloads and decreasing parsing time

When it comes to Emotion, there's a separate post on how Linaria differs from Emotion and styled components.

Want to give it a try? Check Linaria on GitHub, and don't forget to star it. We’re still actively working on it, so please report any issues you find. Pull requests are always welcome.

Insights

Learn more about Developer Experience

cover

May 5Article

Codex Ambassador: Q1 Recap Across 5 Cities

A short recap of Codex events in Berlin, Wroclaw, Warsaw, Sopot, and Miami, plus what we learned from each format and where we want to go next.

cover

Feb 5Live Stream

Storybook Explained: From Basics to Advanced Workflows

Core Storybook concepts, stories and args, interaction testing, and how Storybook works in React Native projects.

cover

Jan 29Article

Announcing AI SDK Profiler for React Native

Announcing the AI SDK Profiler plugin for Rozenite DevTools. Capture OpenTelemetry spans from the Vercel AI SDK and inspect AI requests across on-device and cloud providers.

cover

Nov 28Talk

How I Hacked React Native DevTools (And Built a Plugin Framework)

Learn how React Native DevTools work under the hood, and how to inject custom plugins with clean hooks and no forks.

cover

Sep 16Talk

How I Hacked React Native DevTools (And Built a Plugin Framework)

Learn how React Native DevTools work under the hood, and how to inject custom plugins with clean hooks and no forks.

cover

Jul 28Article

Introducing Rozenite: A Plugin Framework for React Native DevTools

Build custom debugging panels inside React Native DevTools with Rozenite: a modular, TypeScript-ready plugin framework with full device-scoped support.

cover

Feb 12Article

How to Easily Create Native Libraries With Nitro and Turbo Modules

Learn how to optimize React Native performance with Turbo and Nitro Modules. Ditch the legacy bridge and master modern native module development.

cover

Dec 4Podcast

Radon IDE: Better Developer Experience for React Native Teams

Find out how Radon IDE can help increase team velocity and rid you of the common React Native app development pain points.

cover

Sep 19Podcast

Exploring Bun: Jarred Sumner on Hyperfast JavaScript Toolkit

Is Bun the future of React Native package management? Join us as we talk with Jarred Sumner, the creator of this lightning-fast JavaScript toolkit.

cover

Sep 11Podcast

Vim: from Zero to Hero

Discover the power of Vim, the legendary text editor, as we explore its origins, standout features, and practical tips to boost your coding efficiency.

cover

May 15Article

Add-Ons for Web Browsers With React

At times you may need to create an add-on to use a web browser more effectively. Find out how to build one with React.

cover

Jan 27Article

Why Is It Worth to Automate Your Dependency Management with `Autolinking`?

When you automate your dependency management, you can fully focus on developing your app. Find out how you can do it with autolinking.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.