Zero-runtime CSS in JS library

  • By Callstack
  • Last update: Jan 3, 2023
  • Comments: 17

Linaria

Zero-runtime CSS in JS library.


Build Status Code Coverage Version MIT License

All Contributors PRs Welcome Chat Code of Conduct Greenkeeper Sponsored by Callstack

tweet

Features

  • Write CSS in JS, but with zero runtime, CSS is extracted to CSS files during build
  • Familiar CSS syntax with Sass like nesting
  • Use dynamic prop based styles with the React bindings, uses CSS variables behind the scenes
  • Easily find where the style was defined with CSS sourcemaps
  • Lint your CSS in JS with stylelint
  • Use JavaScript for logic, no CSS preprocessor needed
  • Optionally use any CSS preprocessor such as Sass or PostCSS

Why use Linaria

Installation

npm install @linaria/core @linaria/react @linaria/babel-preset @linaria/shaker

or

yarn add @linaria/core @linaria/react @linaria/babel-preset @linaria/shaker

Setup

Linaria currently supports webpack and Rollup to extract the CSS at build time. To configure your bundler, check the following guides:

Or configure Linaria with one of the following integrations:

Optionally, add the @linaria preset to your Babel configuration at the end of the presets list to avoid errors when importing the components in your server code or tests:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@linaria"
  ]
}

See Configuration to customize how Linaria processes your files.

Syntax

Linaria can be used with any framework, with additional helpers for React. The basic syntax looks like this:

import { css } from '@linaria/core';
import { modularScale, hiDPI } from 'polished';
import fonts from './fonts';

// Write your styles in `css` tag
const header = css`
  text-transform: uppercase;
  font-family: ${fonts.heading};
  font-size: ${modularScale(2)};

  ${hiDPI(1.5)} {
    font-size: ${modularScale(2.5)};
  }
`;

// Then use it as a class name
<h1 className={header}>Hello world</h1>;

You can use imported variables and functions for logic inside the CSS code. They will be evaluated at build time.

If you're using React, you can use the styled helper, which makes it easy to write React components with dynamic styles with a styled-component like syntax:

import { styled } from '@linaria/react';
import { families, sizes } from './fonts';

// Write your styles in `styled` tag
const Title = styled.h1`
  font-family: ${families.serif};
`;

const Container = styled.div`
  font-size: ${sizes.medium}px;
  color: ${props => props.color};
  border: 1px solid red;

  &:hover {
    border-color: blue;
  }

  ${Title} {
    margin-bottom: 24px;
  }
`;

// Then use the resulting component
<Container color="#333">
  <Title>Hello world</Title>
</Container>;

Dynamic styles will be applied using CSS custom properties (aka CSS variables) and don't require any runtime.

See Basics for a detailed information about the syntax.

Demo

Edit Linaria Demo

Documentation

Contributing

We appreciate any support in library development!

Take a look on Contributing docs to check how you can run Linaria in development mode.

Trade-offs

  • No IE11 support when using dynamic styles in components with styled, since it uses CSS custom properties

  • Dynamic styles are not supported with css tag. See Dynamic styles with css tag for alternative approaches.

  • Modules used in the CSS rules cannot have side-effects. For example:

    import { css } from '@linaria/core';
    import colors from './colors';
    
    const title = css`
      color: ${colors.text};
    `;

    Here, there should be no side-effects in the colors.js file, or any file it imports. We recommend to move helpers and shared configuration to files without any side-effects.

Interoperability with other CSS-in-JS libraries

Linaria can work together with other CSS-in-JS libraries out-of-the-box. However, if you want to use styled components from Linaria as selectors in styled-components/emotion, you need to use @linaria/interop

Editor Plugins

VSCode

Atom

Webstorm

Sublime Text

Recommended Libraries

Inspiration

Acknowledgements

This project wouldn't have been possible without the following libraries or the people behind them.

Special thanks to @kentcdodds for his babel plugin and @threepointone for his suggestions and encouragement.

Made with ❤️ at Callstack

Linaria is an open source project and will always remain free to use. If you think it's cool, please star it 🌟 . Callstack is a group of React and React Native geeks, contact us at [email protected] if you need any help with these or just want to say hi!

Like the project? ⚛️ Join the team who does amazing stuff for clients and drives React Native Open Source! 🔥

Contributors

Thanks goes to these wonderful people (emoji key):

Paweł Trysła
Paweł Trysła

💻 📖 🤔
Satyajit Sahoo
Satyajit Sahoo

💻 📖 🤔
Michał Pierzchała
Michał Pierzchała

💻 📖 🤔
Lucas
Lucas

📖
Alexey Pronevich
Alexey Pronevich

📖
Wojtek Szafraniec
Wojtek Szafraniec

💻
Tushar Sonawane
Tushar Sonawane

📖 💡
Ferran Negre
Ferran Negre

📖
Jakub Beneš
Jakub Beneš

💻 📖
Oscar Busk
Oscar Busk

🐛 💻
Dawid
Dawid

💻 📖
Kacper Wiszczuk
Kacper Wiszczuk

💻 📖
Denis Rul
Denis Rul

💻
Johan Holmerin
Johan Holmerin

💻 📖
Gilad Peleg
Gilad Peleg

📖
Giuseppe
Giuseppe

💻
Matija Marohnić
Matija Marohnić

💻 📖
Stefan Schult
Stefan Schult

💻
Ward Peeters
Ward Peeters

💻
radoslaw-medryk
radoslaw-medryk

💻
杨兴洲
杨兴洲

💻
Dawid Karabin
Dawid Karabin

📖
Anton Evzhakov
Anton Evzhakov

💻
Chris Abrams
Chris Abrams

💻 📖 🤔
Jayphen
Jayphen

💻
c4605
c4605

💻
Toru Kobayashi
Toru Kobayashi

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

Github

https://github.com/callstack/linaria

Comments(17)

  • 1

    new styles extractor

    Summary

    This is a PoC of more advanced styles extractor. It can evaluate almost any js code whereas current extractor has problems with code like this:

    const object = { fontSize: 12 };
    object.fontWeight = 'bold';
    export default styled.h1`
      ${object};
    `;
    

    Unlike current implementation, this extractor does not evaluate expressions immediately in TaggedTemplateExpression visitor but collects them for later evaluation. Then it builds a dependency graph for all expressions and strips all unrelated code from a module. On the last stage, it does a batch evaluation of all expressions.

    I've created this PR in WIP stage because I had a bit painful merge after 1.3.1 release and I also found your typescript branch :) So if you like it I will do some refactoring and rewrite to TS.

    Test plan

    New tests were added, old tests still work except one test with invalid code and two tests that check dependency extraction during evaluation.

  • 2

    Add an option to output atomic css

    Do you want to request a feature or report a bug? Feature

    What is the current behavior?

    If the current behavior is a bug, please provide the steps to reproduce and a minimal repository on GitHub that we can yarn install and yarn test.

    What is the expected behavior? The current behavior is

    // Input 
    
    const header = css`
      text-transform: uppercase;
      font-family: ${fonts.heading};
      font-size: ${modularScale(2)};
    `;
    
    // Current Output
    const header = 'header_t1ugh8t9';
    
    // Expected Output
    const header = 'tt_swer3 ff_Sers34 fs_5tsdf';
    /*
    tt_swer3  -> text-transform
    ff_Sers34 -> font-family
    fs_5tsdf -> font-size
    */
    

    Please provide your exact Babel configuration and mention your Linaria, Node, Yarn/npm version and operating system.

  • 3

    Update website and readme with logo

    We need it, sooner or later. Already asked my designer-brother (@pierzchala) to work on it in his free time (may be in couple of weeks).

    The logo should resemble concepts like flower, CSS, JS.

  • 4

    Styling custom components with NextJs

    I'm using NextJs with linaria and trying to apply styled on custom component:

    function CoolComponent({ className, style }) {
      return (
        <div className={className} style={style}>My Component</div>
      );
    }
    
    const StyledCoolComponent = styled(CoolComponent)`
      background-color: red;
    `;
    

    and get error:

    import React from "react";
           ^^^^^
    
    SyntaxError: Unexpected identifier
    
  • 5

    Not compatible with Webpack file and url loaders?

    Do you want to request a feature or report a bug? Bug.

    What is the current behavior? Webpack’s file-loader or url-loader (they come standard with create-react-app) let you require() (or import) any file type (like images and fonts). At build time, the require statement gets replaced with a URL that you can use to load the file at runtime. Unfortunately Linaria seems to break any time I reference anything that gets loaded this way. It appears to attempt to load the file as if it were a JS module. E.g. both of these fail:

    css`background-image: url(`require('image.svg')`);`
    css`@font-face { font-family: Test; src: url(${require('webfont.woff')})}`;
    

    If the current behavior is a bug, please provide the steps to reproduce and a minimal repository on GitHub that we can yarn install and yarn test. Quick create-react-app that attempts to load an SVG and a web font on the home page: https://github.com/steadicat/linaria-test

    What is the expected behavior? Expected behavior is for Linaria to let the loader run first so that those requires become plains strings to be included in the CSS.

    Please provide your exact Babel configuration and mention your Linaria, Node, Yarn/npm version and operating system. See app above.

  • 6

    Rollup: Using the "styled" tag in runtime is not supported

    Environment

    rollup: ^1.2.2 linaria: ^1.3.1

    Description

    Project is TypeScript + Linaria transpiled via Babel using @babel/preset-typescript. The module bundler I am using is rollup. Despite having included the linaria/babel preset in my babel.config.js file I am still seeing an error in the console: Error: Using the "styled" tag in runtime is not supported. Make sure you have set up the Babel plugin correctly.

    Reproducible Demo

    /**
     * babel.config.js
     */
    
    module.exports = {
      presets: [
        '@babel/preset-env',
        '@babel/preset-typescript',
        '@babel/preset-react',
        'linaria/babel',
      ],
    };
    
    /**
     * rollup.config.js
     */
    
    import commonjs from 'rollup-plugin-commonjs';
    import resolve from 'rollup-plugin-node-resolve';
    import babel from 'rollup-plugin-babel';
    import linaria from 'linaria/rollup';
    import css from 'rollup-plugin-css-only';
    import pkg from './package.json';
    
    const extensions = ['.js', '.jsx', '.ts', '.tsx'];
    
    const name = 'RollupTypeScriptBabel';
    
    export default {
      input: './src/index.ts',
    
      external: ['react', 'react-dom'],
    
      plugins: [
        // Allows node_modules resolution
        resolve({ extensions }),
    
        // Allow bundling cjs modules. Rollup doesn't understand cjs
        commonjs({
          namedExports: {
            '../../node_modules/linaria/react.js': ['styled'],
            '../../node_modules/react-is/index.js': ['isElement', 'isValidElementType', 'ForwardRef'],
          },
        }),
    
        // Compile TypeScript/JavaScript files
        babel({ extensions, include: ['src/**/*'] }),
    
        linaria({
          sourceMap: process.env.NODE_ENV !== 'production',
          evaluate: true,
        }),
    
        css({
          output: 'styles.css',
        }),
      ],
    
      output: [
        {
          file: pkg.main,
          format: 'cjs',
        },
        {
          file: pkg.module,
          format: 'es',
        },
      ],
    };
    
    
    /**
     * BasicImageViewer.tsx
     */
    import { styled } from 'linaria/react';
    
    const BasicImageViewer = styled.img`
      width: 250px;
      height: 250px;
    `;
    
    export default BasicImageViewer;
    
  • 7

    [1.4.0-beta.0] A build error

    This problem was reported by @Guria (#495)

    Environment

    Description

    node_modules/react-scripts/node_modules/@babel/runtime/helpers/esm/taggedTemplateLiteral.js:1
    export default function _taggedTemplateLiteral(strings, raw) {
    ^^^^^^
    
    SyntaxError: Unexpected token 'export'
    

    Reproducible Demo

  • 8

    Error: Cannot find module 'core-js/modules/es6.regexp.to-string'

    I ran this example on my computer and I got this error:

    ERROR in ./src/App.js Module build failed (from ./node_modules/linaria/loader.js): Error: Cannot find module 'core-js/modules/es6.regexp.to-string' at Function.Module._resolveFilename (internal/modules/cjs/loader.js:613:15) at Function.Module._load (internal/modules/cjs/loader.js:539:25) at Module.require (internal/modules/cjs/loader.js:667:17) at require (internal/modules/cjs/helpers.js:20:18) at Object.<anonymous> (/Users/egortrubnikov-panov/Downloads/linaria-demo 2/node_modules/linaria/lib/loader.js:3:1) at Module._compile (internal/modules/cjs/loader.js:738:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:749:10) at Module.load (internal/modules/cjs/loader.js:630:32) at tryModuleLoad (internal/modules/cjs/loader.js:570:12) at Function.Module._load (internal/modules/cjs/loader.js:562:3)

    how can I fix this?

  • 9

    [website] Replace ESLint with Prettier

    Do you want to request a feature or report a bug? Feature (Build tools)

    What is the current behavior? ESLint rules are so annoying and slow down the development.

    If the current behavior is a bug, please provide the steps to reproduce and a minimal repository on GitHub that we can yarn install and yarn test. N/A

    What is the expected behavior? Prettier is the standard currently and so many popular projects use it and makes you write the code without being worried about one less semicolon or on which line close which tag :(

    Please provide your exact Babel configuration and mention your Linaria, Node, Yarn/npm version and operating system. Not related.

  • 10

    Fix dual packages ES module import bug

    Fixes #904 (and possibly #1043)

    Solution is based on this documentation: https://nodejs.org/api/packages.html#approach-1-use-an-es-module-wrapper

    It properly configures the package entry point for both CommonJS and ESM, which is currently not the case (see #904)

  • 11

    EvalError: Unexpected token 'export' in

    Environment

    • Linaria version: 4.1.0
    • Bundler (+ version): webpack 5.78
    • Node.js version: 16
    • OS: ubuntu

    Description

    ➤ YN0000: /home/circleci/project/node_modules/@linaria/babel-preset/lib/module.js:384
    ➤ YN0000:         throw new EvalError(`${e.message} in${callstack.join('\n| ')}\n`);
    ➤ YN0000:         ^
    ➤ YN0000: 
    ➤ YN0000: EvalError: Unexpected token 'export' in
    ➤ YN0000: | /home/circleci/project/node_modules/antd/es/index.js
    ➤ YN0000: | src/app/App.tsx
    ➤ YN0000: 
    ➤ YN0000:     at /home/circleci/project/node_modules/@linaria/babel-preset/lib/module.js:384:15
    ➤ YN0000:     at Array.forEach (<anonymous>)
    ➤ YN0000:     at Module.evaluate (/home/circleci/project/node_modules/@linaria/babel-preset/lib/module.js:361:10)
    ➤ YN0000:     at require.Object.assign.ensure (/home/circleci/project/node_modules/@linaria/babel-preset/lib/module.js:319:11)
    ➤ YN0000:     at src/app/App.tsx:1:46
    ➤ YN0000:     at src/app/App.tsx:2:3
    ➤ YN0000:     at Script.runInContext (node:vm:139:12)
    ➤ YN0000:     at /home/circleci/project/node_modules/@linaria/babel-preset/lib/module.js:368:16
    ➤ YN0000:     at Array.forEach (<anonymous>)
    ➤ YN0000:     at Module.evaluate (/home/circleci/project/node_modules/@linaria/babel-preset/lib/module.js:361:10)
    

    Seems related to babel doing a transform it shouldn't?

    Reproducible Demo

    https://github.com/ntucker/anansi/pull/1591

    1. git clone https://github.com/ntucker/anansi
    2. git checkout renovate/major-linaria
    3. yarn install
    4. yarn build:pkg
    5. cd examples/concurrent
    6. yarn build
  • 12

    fix: preeval emits invalid code

    Fix some instances where preeval emits invalid code.

    Motivation

    Avoid breaking builds due to buggy preeval logic.

    Summary

    Unfortunately, babel seems to record identifiers as globals even when they're accessed via TS type references, e.g:

    class Foo {
      fetch: typeof global.fetch;
    }
    

    will result in scope.hasGlobal('fetch') returning true. preeval will end up deleting the class property, and then the entire class. The same applies if fetch is accessed in a member expression, e.g

    class Foo {
      fetch: typeof global.fetch;
      constructor(options) {
        this.fetch = options.fetch;
      }
    }
    

    The key is the typeof global.fetch messes up Babel's scope tracking, which makes subsequent isGlobal/isBrowserGlobal checks invalid.

    A similar problem occurs with TS type references - accessing window.foo or global.foo will break the scope tracking, resulting in TS type references that look like globals getting deleted. See the unit test for an example.

    Test plan

    Added unit tests

  • 13

    fix: exports.hasOwnProperty is not a function

    The Proxy for this.#exports did not forward unknown properties to the underlying Object instance.

    Motivation

    Fixes issues like https://github.com/callstack/linaria/issues/1140

    Summary

    Ensure that the Proxy for this.#exports forwards unknown properties to the underlying Object instance.

    Test plan

    Added a unit test.

  • 14

    [Release request] Release v3 minor version with atomic styled support

    Describe the feature

    Release a v3.1 (or whatever minor version is preferred) containing the styled atomic support in https://github.com/callstack/linaria/pull/966, without the changes in 3.0.0-beta.19.

    Motivation

    I work with @jpnelson and we're currently running Linaria with atomic styles on 3.0.0-beta.18, which has been stable and working well for us over the past 6 months or so. We'd like to use the styled atomic support contributed by @juanferreras in https://github.com/callstack/linaria/pull/966, but it was never published to NPM. Instead, 3.0.0-beta.19 was released, which contains a major refactor to how tags are resolved, and also removes the libResolver option[^1] that was contributed in https://github.com/callstack/linaria/issues/851. v4 contains even bigger changes and we've run into several issues with shaker/preeval on our codebase, so we'd like to hold off on those upgrades for now.

    I'll be putting up several PRs for issues we found while testing a v4 upgrade, so hopefully we can get up to date later this year!

    [^1]: We're using libResolver as a way to enable atomic styles across our codebase without needing to make code changes (via swapping @linaria/core and @linaria/atomic imports).

    Possible implementations

    Cut a release at this revision: https://github.com/callstack/linaria/commit/f59860b09c5f91b0423dbf188e5f8aaaef38a6b5

  • 15

    @linaria/babel-preset deletes code unexpectedly

    Environment

    • Linaria version: @linaria/babel-preset v4.3.2
    • Bundler (+ version): N/A
    • Node.js version: 16.17.0
    • OS: Mac, Linux (codesandbox)

    Description

    We use @linaria/babel in server builds to ensure that server side rendering (e.g ReactDOMServer.renderToString) produces the same classnames as the browser build. However, it appears that this babel transform also deletes exports unexpectedly if they are referenced in the css template literal.

    I believe the root cause is the fact that processTemplateExpression will inject a __linariaPreval export as well as various temporary _exp variables. Linaria tries to clean them up: https://github.com/callstack/linaria/blob/3ce985e0f7448dca4299adcd90c3f8bb5c6197e4/packages/babel/src/plugins/babel-transform.ts#L77-L83

    but removeWithRelated appears to have a bug where it's deleting exports.

    The same bug happens when using the transform method exported by @linaria/babel-preset, due to the similar code here: https://github.com/callstack/linaria/blob/3ce985e0f7448dca4299adcd90c3f8bb5c6197e4/packages/babel/src/plugins/collector.ts#L55-L61

    In general this approach of mutating the input file to add and remove Linaria temporary variables feels like a bad idea; the only modifications that Linaria makes to the input ought to be replacing tags.

    Reproducible Demo

    https://codesandbox.io/p/sandbox/stupefied-hellman-r0kbh0?file=%2Finput.js&selection=%5B%7B%22endColumn%22%3A10%2C%22endLineNumber%22%3A8%2C%22startColumn%22%3A10%2C%22startLineNumber%22%3A8%7D%5D

  • 16

    Can V4 remove the configuration of babelOptions when using language features?

    Describe the enhancement

    The V3 doesn't require the babelOptions when using Vite with outbox TS language. V4 requires specifying the babel options, but I'm using Typescript decorators, which are different from the Babel decorators. I can't configure the decorator's features through babel options.

    See this issue for details: https://github.com/babel/babel/issues/8864

    Is there a possible enhancement for not configuring the babel options just like the V3 does?

    Motivation

    Using full featured Typescript decorators.

    Possible implementations

    Related Issues

  • 17

    All props are filtered out when using "as" props

    Environment

    • Linaria version: 4.3.2
    • Bundler (+ version): "webpack": "5.75.0"
    • Node.js version: v14.19.3
    • OS: Codesandbox

    Description

    Linaria uses static mechanism to detect if the styled component should pass props https://github.com/callstack/linaria/pull/1101 However, when you use "as" prop the static mechanism is not working and component that you pass in "as" doesn't have props.

    Reproducible Demo

    https://codesandbox.io/s/linaria-as-iuhnl8?file=/src/App.tsx