As frontend applications and component-based architecture continue to grow in scope and complexity, one area that poses increasing challenges is writing modular, maintainable stylesheets. The cascading nature of traditional CSS makes it easy to end up with selector name collisions, dead code cruft, and difficulties encapsulating styles, and this has led more developers to turn to inline styles.
Inline styles allow you to bundle markup, CSS, and functionality of components in one place, avoiding the hassle of switching between your HTML and an external CSS file, and reducing the error-prone nature of indirect style references (via selector logic).
However, inline styles are generally unpopular due to their lack of separation issues, less reusability, and specificity issues. Added to the fact that media queries and pseudo-classes like :hover and :active cannot be used with inline CSS, the reservations developers have about inline styles are justified.
This led to the invention and rise of CSS-in-JS, which generates and injects style sheets into the DOM in real time. CSS-in-JS retains the encapsulation benefits of inline styles while regaining traditional CSS flexibility for dynamism, maintainability, and reusability. But like all things, it has its downsides, which brings us to CSS Hooks, a unique solution.
In this article, we’ll look in-depth at how CSS Hooks works and the advantages it delivers for component styling. We’ll explore where CSS Hooks fits in the broader CSS-in-JS ecosystem — which offers diverse techniques with their tradeoffs. We’ll also analyze the current state of CSS-in-JS regarding recent changes and the future direction across JavaScript frameworks.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
CSS Hooks is a React-based solution that allows you to declare stylistic rules using hooks. CSS Hooks brings the encapsulation and dynamism of CSS-in-JS to React apps while keeping the API familiar for React developers by using hooks. It makes features like scoped styles, media queries, etc., available via inline styles. This improves on many of the traditional pain points around writing CSS in JS.
This approach promotes cleaner and more maintainable code by keeping styles tied to their corresponding components, while under the hood, CSS Hooks handle dynamically injecting stylesheets and generating class names tied to components, providing per-component scoping without naming collisions.
CSS Hooks doesn’t try to reinvent the wheel by creating something new. It instead focuses on expanding what’s possible with inline styles, rather than fully replacing them with something different. You can now leverage a full range of dynamic CSS features with inline styles: pseudo-classes, complex selectors, media queries, and additional capabilities – all while keeping the simple, buildless approach inline styles are known for.
For example, CSS Hooks newly enables using the :hover or :active pseudo-classes and more – all without changes to existing syntax. It may seem too good to be true that inline styles can now support this level of dynamism and reuse. But CSS Hooks makes it a reality – no more runtime injection or build steps required. Here’s how it works.
CSSHooks works with React, Prereact, Solid.js, and Qwik, and we’re going to use Vite with the React configuration. First, let’s create a project called css-hooks and install Vite:
$ npm create vite@latest css-hooks -- --template vanilla-ts
Next, we’ll enter the folder we’ve created and install the CSS Hooks library:
cd css-hooks-playground npm install && npm install @css-hooks/react
So far, we’ll have a basic Vite template page that looks like this:

Next, we need to create our CSS Hooks system. To do this, we’ll create a css.ts file in the src folder and input the following code:
import { createHooks } from "@css-hooks/react";
export const { styleSheet, css } = createHooks({
hooks: {
"&:active": "&:active",
"&:hover": "&:hover",
},
debug: import.meta.env.DEV,
});
With this, we import the buildHooksSystem function from the @css-hooks/core library and then call the buildHooksSystem function to create the hook system. The resulting createHooks function will be used to define hooks and manage styles.
If you’re using CSS Hooks on a framework outside the ones mentioned above, you’d have to add a styleObjectToString function that takes a JavaScript object representing CSS styles, filters out invalid entries, formats the property names according to CSS conventions, and joins them into a single string, suitable for use 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("; ");
}
This functionality is usually bundled within app frameworks, so if you’re using React, Prereact, Solidjs, or Qwik, you don’t have to add this to your css.ts file.
Next, we’ll go to the main.ts file and import the stylesheet into the app. We’ll go to the src/main.tsx file, import the stylesheet, and inject dynamically generated CSS styles using the styleSheet function:
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>,
)
With this, we can now use :hover and :active as inline styles within our code. To test this out, we will target the counter button, and update it so that it increases in size when clicked (active), and the color changes on hover, implementing all of these modifications using inline styles. To do that, we have to go to the app.tsx folder and add the following code to the button:
<button
onClick={() => setCount((count) => count + 1)}
style={css({
transition: "transform 75ms",
on: $ => [
$("&:active", {
transform: "scale(5)"
}),
$("&:hover", {
background: "#1b659c",
}),
$("&:active", {
background: "#9f3131",
})
]
})}
>
count is {count}
</button>
Here’s the result:

With this, we’ve successfully installed and used CSS Hooks, an elegant solution to using pseudoclasses with inline styles.
With component-based architecture becoming more prevalent in recent years, CSS-in-JS has become a mainstream approach for styling web applications, offering benefits like component encapsulation, dynamic styling, and improved developer experience. However, recent advances pose a significant problem for CSS-in-JS libraries.
The introduction of Server Components in React 18 and Next.js represents a shift towards optimizing the amount of JavaScript shipped to the browser. By having certain components rendered on the server, less JS needs to be hydrated on the client.
However, this poses a challenge for CSS-in-JS libraries, which depend heavily on client-side JavaScript and hydration. The dynamically generated styles from CSS-in-JS need to exist on page load to avoid a jarring style flash between server-rendered and hydrated output. Libraries like Styled Components handle this by injecting collected styles during the hydration process.
But with Server Components, there is now a split between the static server-rendered markup, and the remaining JS sent to hydrate the page. This fragmentation means most current CSS-in-JS solutions will face difficulties inferring which styles need to be injected during hydration, because both the styling code and components themselves may straddle server and client.
This could require refactoring on the part of CSS-in-JS libraries to cleanly separate server-rendered components which need pre-injected styles, versus client components that handle their hydration. Features like dynamic values also pose challenges for server rendering and hydration consistency, so new conventions around injecting styles for server components while avoiding duplication may be needed.
In summary, the shift towards server-side rendering encouraged by React and Next.js creates an urgency around refining CSS-in-JS hydration approaches. We’re now dealing with a split environment where both static and hydrated UI coexist, making it crucial to smooth out these integration processes.
Some libraries are already adapting to that, and CSS Hooks is one of those libraries well-positioned to address some of the challenges posed by server-side rendering and components. CSS Hooks’ architecture is built around component scoping, static extraction, deferred hydration modes, and universal rendering awareness to help smoothly adapt to the constraints posed by modern server-side rendering needs. CSS Hooks allows for building apps optimized for server rendering without giving up the advantages of CSS-in-JS techniques or requiring duplicate styling logic.
CSS-in-JS sits at a fascinating point in its evolution—with rapid innovation and a wide diversity of approaches, yet still in search of stability and standardization. New libraries continue emerging while existing ones iterate through various versions and syntax changes.
In many ways, CSS-in-JS represents the cutting edge of component styling. Yet with so many pieces still in motion, it may require another few years before clear winners emerge across the greater frontend ecosystem.
For now, CSS-in-JS operates more as a broad category of techniques than a unified approach, and for developers struggling with CSS shortcomings and scaling frontend code complexity, CSS Hooks provides a strong solution to level up component styling.
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks.
LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.
Modernize how you debug web and mobile apps — start monitoring for free.

line-clamp to trim lines of textMaster the CSS line-clamp property. Learn how to truncate text lines, ensure cross-browser compatibility, and avoid hidden UX pitfalls when designing modern web layouts.

Discover seven custom React Hooks that will simplify your web development process and make you a faster, better, more efficient developer.

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
3 Replies to "CSS Hooks and the state of CSS-in-JS"
Thanks for sharing this! I found your information really helpful. Your explanations were easy to follow, and I appreciated how you broke down complex ideas into simple steps. Keep the posts coming! Very good talent.
What challenges arise in writing modular, maintainable stylesheets as frontend applications and component-based architecture expand in complexity?
We recommend checking out the article above, as it touches on issues like selector name collisions, dead code, and more as a precursor to a discussion of how inline styles and CSS-in-JS solutions evolved to address those challenges. Hope you enjoy your read!