Back

CSS Hooks and the Evolution of CSS-in-JS

CSS Hooks and the Evolution of CSS-in-JS

The rise of CSS-in-JS has made it popular for producing and injecting style sheets into the DOM in real timereal time while still maintaining the advantages of inline styles. Similar to classic CSS, it offers flexibility for dynamism, maintainability, and reusability. However, CSS-in-JS has its drawbacks, and that’s where CSS Hooks come in as a unique solution. This article will provide an extensive view of CSS Hooks and their benefits for component styling.

Although traditional CSS’s cascading nature is beneficial in many cases, it has also created issues. Developers are increasingly concerned about problems like selector name clashes, the accumulation of dead code cruft, and the inherent difficulty in encapsulating styles within component boundaries. Consequently, many have explored alternatives that provide more modularity, maintainability, and flexibility. One such example of a popular alternative is inline styles.

Inline styles allow you to combine HTML markup, CSS, and component functionality in the same place, eliminating the need to switch between your HTML and an external CSS file. This reduces the possibility of errors from indirect style references through selector logic.

However, inline styles have some disadvantages. They lack separation, which reduces reusability, and they raise specificity concerns. Additionally, inline CSS cannot be used with media queries and pseudo-classes like :hover and :active. These concerns are valid and should be considered by developers when deciding whether to use inline styles.

We’ll explore how CSS Hooks fits into the larger CSS-in-JS ecosystem, which includes various approaches with different tradeoffs. We’ll also take a look at the evolution and current state of CSS-in-JS, including recent updates and its future path across JavaScript frameworks.

Understanding CSS Hooks

CSS Hooks is a React-based solution that lets you define style rules using hooks. It combines CSS-in-JS’s encapsulation and dynamic nature with React apps while keeping the API familiar to React developers. This allows for capabilities like scoped styles and media queries to be accessible through inline styles, which helps to address many of the traditional issues encountered when writing CSS with JS.

CSS Hooks can keep styles attached to their respective components, resulting in clearer and more manageable code. The library handles dynamically injecting stylesheets and creating component-specific class names, thereby allowing for per-component scoping without naming clashes.

Mechanism of CSS Hooks

Enhancing Inline Styles with CSS Hooks

CSS Hooks does not aim to create something entirely new but rather enhances the capabilities of inline styles. It allows for a complete range of dynamic CSS features, such as pseudo-classes, complicated selectors, media queries, and other capabilities. All of this is done while maintaining the basic, buildless approach for which inline styles are known.

With CSS Hooks, you can use the :hover and :active pseudo-classes, among other features, without changing the existing syntax. This is the level of dynamism and reusability that inline styles provide, but CSS Hooks makes it a reality. This eliminates the need for runtime injection or build processes. Here’s how it works.

CSSHooks supports React, Preact, and Solid.js among others. In this section, we’ll use Vite in the React configuration. First, let’s create a project named css-hooks and install Vite.

npm create vite@latest css-hooks -- --template react-ts
cd css-hooks
npm install
npm run dev

Next, let’s install the CSS Hooks library.

npm install @css-hooks/react

Next, Open the generated URL, and you should have something similar to the image shown below: Screenshot 2024-04-17 at 10.11.17 Now, we need to develop our CSS Hooks system by creating a css.ts file in the src folder and adding the necessary code:

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

export const { styleSheet, css } = createHooks({
  hooks: {
    "&:active": "&:active",
 },
  debug: import.meta.env.DEV,
});

To create a hook system, we must import and call the buildHooksSystem function from the @css-hooks/core library. This will generate a createHooks method, which we can use to specify hooks and styles.

If you’re using CSS Hooks on a framework other than the ones listed above, you must include a styleObjectToString function. This function should take a JavaScript object that represents CSS styles, filter out invalid entries, format the property names according to CSS conventions, and join them into a single string that can be used as inline styles.

export function styleObjectToString(obj: Record<string, unknown>) {
  return Object.entries(obj)
 .filter(
 ([, value]) => typeof value === "string" || typeof value === "number",
 )
 .map(
 ([property, value]) =>
        `${/^--/.test(property) ? property : property.replace(/[A-Z]/g, x => `-${x.toLowerCase()}`)}: ${value}`,
 )
 .join("; ");
}

Next, we’ll open the main.ts file and import the stylesheet into the application. To do this, we’ll navigate to src/main.tsx, import the stylesheet, and use the styleSheet function to dynamically inject the created CSS styles

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { styleSheet } from './css.ts'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <style dangerouslySetInnerHTML={{ __html: styleSheet() }} />
    <App />
  </React.StrictMode>,
)

We can apply :active effects to HTML elements by using inline styles in our code. To illustrate this, let’s select the button and modify it so it enlarges when clicked (active). All of this will be done using inline styles. To achieve this, go to the app.tsx folder and add the following code to the button:

import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { css } from './css.ts'

function App() {

  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Getting started with CSS Hooks</h1>
      <div className="card">
      <button
          
          style={css({
            transition: "transform 75ms",
            on: $ => [
              $("&:active", {
                transform: "scale(2)"
 })
 ]
 })}
        >
 click me to see the effect
        </button>
      </div>
    </>
 )
}

export default App

The following result shows the output of the code above, as shown in the image below: jerry

Evolution and Current State of CSS-in-JS

CSS-in-JS has become a popular approach for decorating online applications due to the growing prevalence of component-based architecture. It offers various advantages such as improved developer experience, dynamic styling, and component encapsulation. However, new developments such as Server Components in React 18 and Later.js present a significant challenge for CSS-in-JS libraries.

Server Components aim to reduce the amount of JavaScript transmitted to the browser, resulting in less JS that needs to be hydrated on the client when some components are rendered on the server. This poses a problem for CSS-in-JS libraries as they rely mainly on hydration and client-side JavaScript. To avoid any jarring style flash between server-rendered and hydrated output, dynamically produced styles from CSS-in-JS must be present when the page loads. During the hydration process, libraries like Styled Components inject accumulated styles.

With Server Components, there is a new distinction between static server-rendered HTML and the remaining JavaScript (JS) used to load the page. This fragmentation may pose a challenge for most existing CSS-in-JS solutions as they may have trouble determining which styles should be injected during the loading process. This is because the styling code and components themselves may span both the server and the client.

CSS-in-JS libraries may need to be modified to better distinguish between server-rendered components that require pre-injected styles and client components that handle their hydration. Dynamic values can cause additional issues for server rendering and hydration consistency, which requires the development of new rules for injecting styles for server components while avoiding duplication.

In summary, React and Next.js are pushing for server-side rendering, making improving CSS-in-JS hydration methodologies more urgent. We now have a split environment where static and hydrated UI coexist, so it is critical to streamline these integration operations.

Some libraries have already adapted to this. CSS Hooks is one such library that is well-positioned to address some of the issues raised by server-side rendering and components. CSS Hooks has an architecture based on component scoping, static extraction, deferred hydration modes, and universal rendering awareness. This allows it to adapt to the requirements of modern server-side rendering.

Conclusion

CSS-in-JS is currently going through an exciting phase in its development, characterized by rapid innovation and a wide range of options. Despite the diversity of libraries available, the CSS-in-JS community is still seeking standardization and stability. This means that new libraries are constantly being developed while existing ones evolve through various versions and syntactic modifications.

In many ways, CSS-in-JS is pushing the boundaries of component styling. However, with so many moving parts, it may take a few years before the most effective and widely adopted approaches emerge throughout the entire frontend ecosystem.

At present, CSS-in-JS is more of a broad category of techniques than a unified approach. For developers grappling with CSS limitations and the growing complexity of frontend code, CSS Hooks offers a robust solution to improve component styling.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay