Skip to content

CSS Hooks: A new way to style your React apps

CSS Hooks: A new way to style your React apps

In the world of web development, the management of styles has always been a crucial aspect of building modern and responsive user interfaces. With the rise of CSS in JS libraries like Material UI and Chakra, developers have started creating dynamic and reusable styles using JavaScript; however, the performance implications of these libraries have led to the exploration of alternative solutions. One such solution is CSS Hooks, a library that offers a different approach to managing styles in web applications.

The Problem with CSS in React and CSS in JS Libraries

In React applications, you typically write styles directly with the style prop by applying classes with className, or if you’re using a framework like Material UI or Chakra then you may use the sx attribute, which is more powerful than the baseline attributes. There are some performance concerns in the case of the sx attribute specifically. The framework has to dynamically generate classes with your styles and apply them to the document whenever it is used.

This can become an issue with large and complex applications as your styles will be regularly regenerated on the fly as the user navigates through the application. This has led developers to seek alternative solutions that offer similar flexibility but with better performance.

Introduction to CSS Hooks

CSS Hooks improve upon the aforementioned issues by pre-generating your CSS using configuration hooks. The styles that these hooks generate remain static during the lifetime of the application and are reused wherever possible. CSS variables are utilized under the hood whenever dynamic behavior is needed. By dynamic behavior, I’m referring to advanced CSS features such as pseudo-class selectors and media queries, which are both traditionally unavailable for use with inline styles. CSS Hooks utilize an interesting trick under the hood to accomplish this, which we will now explore.

The Power of CSS Variables and the Fallback Trick

As mentioned before, CSS variables are utilized in order to support using advanced dynamic behavior such as pseudo-selectors in our inline styles. CSS variables are core to something known as the “fallback trick”. The gist of how the fallback trick works is to have the pseudo-selector or media query toggle the state of a variable between initial and an empty invalid value, and then we put the value that we actually want to toggle as the “fallback” value in the reference to the CSS variable inside of the inline style itself.

Changing this fallback value inside of an inline style essentially allows us to control the values used by the pseudo-selector without having to regenerate linked stylesheets as we only have to change the inline style attribute. This is best explained with an example:

<a
  href="https://example.com"
  style="color: var(--focus-true, #6a49ce) var(--focus-false, #3f6ab9)"
>
  Go to example.com
</a>

<style>
  * {
    --focus-false: initial;
    --focus-true: ;
  }

  :focus {
    --focus-false: ;
    --focus-true: initial;
  }
</style>

This plain HTML and CSS code effectively allows us to utilize the focus pseudo-selector on any element that we want using inline styles only. This is what CSS Hooks effectively does in the background when you use dynamic styles. No nasty re-renders and mucking with stylesheets needed!

How to use CSS Hooks

Now that we know how CSS Hooks fundamentally work, let’s try using them. The library itself is much easier to use than the aforementioned example. Installation and usage of CSS Hooks is easy. Firstly, we must install the @css-hooks/react package into a React project with the package manager of your choice (npm, pnpm yarn, etc). Once that’s done, all that’s left is a little bit of configuration.

You have to first use a utility function called createHooks that is exported from the package we just installed. The result of that function returns a couple of variables in an array that can be deconstructed, the first being a stylesheet in the form of a string, and the second being a function.

import { createHooks } from "@css-hooks/react";

export const [hooks, css] = createHooks({});

I recommend putting this in a separate file that can be imported from multiple other places in the project as this is where we will be configuring hooks across the project. We’re exporting both hooks and css, and both are important.

We need to include hooks into the DOM as it contains the styles of the hooks that we’ll be referencing in our components. In your root component you need to add the following <style> tag so that the generated CSS can be used.

import { hooks } from "./css-hooks";

function App() {
  return (
    <>
      <style dangerouslySetInnerHTML={{ __html: hooks }} />

			...
    </>
  );
}

export default App;

How this is done may vary based on the libraries that you are using. After that’s done, you can integrate CSS Hooks into your components while writing inline styles mostly just like you would do normally, with the only change being the addition of an additional call to the css function that we exported earlier:

import { css } from "./css-hooks";

function SomeReactComponent(props) {
	return (
		<button
			style={css({
				padding: 0,
				margin: 0,

				...
			})}
		>
			{props.text}
		</button>
	)
}

Now that the core boilerplate is set up, your components can now utilize CSS Hooks in theory; however we need to actually add some hooks to start with before we can use them, or else we’ll just be limited to basic styles in the same way that React inline styles are.

Make your own hooks

We can define our hooks inside of the css-hooks file that we created earlier, the same one that exports the css helper function that gets CSS Hooks working with our components. Hooks are defined in the options parameters that are currently empty. Defining a simple hook that allows us to define inline hover styles for a component is very easy and can be done as follows:

import { createHooks } from "@css-hooks/react";

export const [hooks, css] = createHooks({
	"&:hover": "&:hover",
});

Although both the key and the value in this example are the same, they both serve different purposes. The key is the name of the hook that we reference when we want to use it, while the value is the spec. The spec is the actual CSS selector that we’re writing. The name can be anything that you want it to be so long as it doesn’t clash with any existing style properties or hooks.

Although making your own hooks may be necessary at times, there is another repository provided by the CSS Hooks authors that adds a way to generate many useful hooks called @css-hooks/recommended. I highly recommend using this library to generate your hooks if at all possible, and the usage is pretty simple. Here’s an example showing how you can use it to generate media queries, color scheme specific styles and pseudo-selectors:

import { createHooks } from "@css-hooks/react";
import { recommended } from "@css-hooks/recommended";

export const [hooks, css] = createHooks(recommended({
  breakpoints: ["500px", "1000px"],
  colorSchemes: ["dark", "light"],
  pseudoClasses: [
    ":hover",
    ":focus",
    ":active",
    ":disabled",
  ]
}));

Then with that done, these can be utilized as follows:

import { css } from "./css-hooks";

export default function Home() {
  return (
    <main
      style={css({ maxWidth: 1200, margin: "0 auto", textAlign: "center" })}
    >
      <h1>CSS Hooks Example</h1>
      <p>Here is an example of CSS Hooks in action!</p>

      <a
        href="https://example.com"
        style={css({
          color: "cornflowerblue",
          "&:hover": { color: "red" },
        })}
      >
        Hover over me to make me red
      </a>
    </main>
  );
}

We can also modify the hooks configuration to add our own hooks in addition to the recommended hooks by using the spread operator:

import { createHooks } from "@css-hooks/react";
import { recommended } from "@css-hooks/recommended";

export const [hooks, css] = createHooks({
  ...recommended({
    breakpoints: ["500px", "1000px"],
    colorSchemes: ["dark", "light"],
    pseudoClasses: [":hover", ":focus", ":active", ":disabled"],
  }),

  // Your own custom hooks can go here.
  customFocusHook: "&:focus",
});

Then that custom hook can be used by simply using its key name:

<a
  href="https://example.com"
  style={css({
    color: "cornflowerblue",
    "&:hover": { color: "red" },
    customFocusHook: { color: "lime" },
  })}
>
  Hover over me to make me red
</a>

Summary

CSS Hooks offers a performant alternative to traditional CSS in JS libraries by leveraging CSS variables and providing a unique approach to managing styles in web applications. By pre-generating the stylesheet based on configured hooks and utilizing CSS Variables for dynamic behavior, developers can create reusable styles without the performance overhead associated with dynamic stylesheet generation.

If you're interested in exploring CSS Hooks further, be sure to check out @CSSHooks for more information. You can also find some of the examples demonstrated here in a working app in our demos repository. Thank you for reading!