How to use Linaria CSS-in-JS with RedwoodJS

Redwood is agnostic about how you choose to style your app. Given the opinionated conventions elsewhere in the project, this freedom to bring your own styling approach may be surprising, but given the wide variety of styling choices available, and the tradeoffs amongst those approaches, this flexibility makes more sense. By not prescribing a specific styling approach, Redwood enables better tailoring for specific use cases (whether you’re adopting a component framework like Grommet or Material UI , or optimizing for a traditional external CSS file approach)

Redwood’s scaffolding generator uses Tailwind CSS for styling generated components, but there’s no reason a Redwood user is committed to using Tailwind. In fact, Redwood makes it quite easy to bring your own preferred tool. Let’s examine building the Contact form in the Redwood tutorial with a CSS-in-JS approach.

For this example, we’ll be using Linaria . Linaria differs from other CSS-in-JS approaches with its lack of reliance on a runtime dependency. Instead, Linaria uses the power of CSS variables for enabling dynamic prop based styling in React components, while still generating static CSS files. It also enables co-location of CSS with React components, and creating scoped selectors to keep the styles outside the global scope. For my tastes, it brings the best of both worlds: the full power of CSS, with the logical benefits of co-locating CSS with JS (and enabling dynamic styling). Josh Comeau has a great post exploring the benefits of using CSS variables for CSS-in-JS.

First, we need to add Linaria support to our Redwood project. We’ll be picking up on the redwoodblog tutorial project on the form creation step.

First let’s add our Linaria dependency yarn add linaria . Integrating Linaria with Webpack also introduces dependencies on css-loader and mini-css-extract-plugin , but those are included in Redwood itself.

Now that we have Linaria, the first thing we need to do is get Babel up and running. There’s already a local .babelrc file in your src/web folder which extends the base babel config. The only thing we need to do here is add an additional preset:

module.exports = {
  extends: "../babel.config.js",
  presets: [

Next we need to hook up Webpack. This part is a little less straightforward. First we need to create our own webpack config file extending the Redwood config. You’ll need to add webpack.config.js to web/config - keep in mind the config directory doesn’t exist and you’ll need to add it manually.

Within that new webpack.config.js file, you want to add code to extend the existing webpack config. Specificallyt, we’ll be overwriting the base Redwood module rules, which you can find on Github. JS rules are the third element in rules[0].oneOf array, so we’ll extend the existing rules

module.exports = (config, { env }) => {
  const jsRules = config.module.rules[0].oneOf[2]
  config.module.rules[0].oneOf[2] = {
    use: [
        loader: 'linaria/loader',
        options: {
          sourceMap: env !== 'production',
  return config

Restart your Redwood dev server, and you should be ready to go! Let’s find out.

The redwood blog tutorial asks you to add styling by creating a specific CSS file. But with Linaria, we can drop the files in the same location as our contact form! Within the ContactPage file, add a Linaria import: import { css } from 'linaria' . Next you’ll be dropping in the same style blog as the tutorial:

const stForm = css`
  textarea {
    display: block;
    outline: none;

  label {
    margin-top: 1rem;

  .error {
    color: red;

  textarea.error {
    border: 1px solid red;

Now add your generated scoped class to your form:

    validation={{ mode: 'blur' }}

And that’s it. Linaria does the rest. There’s a lot more exciting ways of using Linaria, which you can find in their docs. But this will get you started!