High Five Studio

June 2026

Why I Stopped Using CSS-in-JS for Croatian Front-End Projects

A front-end developer reflects on why CSS-in-JS slowed down Croatian projects and why he switched back

Why I Stopped Using CSS-in-JS for Croatian Front-End Projects

A few years ago, I was fully on the CSS-in-JS bandwagon. For every new Croatian startup or e-commerce site I built, styled-components was my default choice. It promised component isolation, dynamic styling, and the end of global CSS conflicts. But after a string of projects that felt increasingly heavy and slow to load, I started asking myself a hard question: was I making my sites better, or just making my own developer experience more comfortable?

The reality for a Croatian audience is specific. We often serve users on older hardware, with inconsistent mobile network coverage, especially outside of major cities like Zagreb, Split, or Rijeka. A 50KB JavaScript bundle for styling might be fine in San Francisco on a gigabit connection, but it can create a noticeable delay for a user in Slavonia on a 4G signal. That delay, that moment of white screen before the styled button appears, started to feel like a compromise I wasn't willing to make anymore.

The Hidden Performance Tax on Croatian Users

The most immediate reason I walked away from CSS-in-JS was performance. Every time you use a styled component, you are not just writing CSS; you are writing JavaScript that will generate that CSS at runtime. This has a cost.

Runtime Overhead and Bundle Size

Let's break down what happens. A traditional CSS file is small, text-based, and parsed almost instantly by the browser. A CSS-in-JS library, on the other hand, needs to parse your template literals, inject style tags into the DOM, and compute dynamic values based on props. This all happens after your JavaScript bundle is downloaded and executed.

For a Croatian audience, where data costs can be a concern and network speeds vary wildly, every kilobyte counts. A simple button component with styled-components can add 2–3KB to your bundle. Multiply that by dozens of components, and you are looking at 100–200KB of pure styling logic. That is not just CSS; that is JavaScript that the browser has to parse, compile, and execute before the user sees a styled page.

The "Flash of Unstyled Content" Problem

With traditional CSS, the browser can start rendering your page almost immediately. With CSS-in-JS, you often need the JavaScript engine to be fully booted up before any styles are applied. This leads to a brief but noticeable flash of raw HTML or a blank screen.

I noticed this acutely on a project for a local Croatian news portal. The site was built with Next.js and styled-components. On a desktop in Zagreb, it was fine. On a mid-range phone connected to a 3G network in a rural area, the site would show a white screen for nearly a second before the header appeared. That second felt like an eternity. The user was left wondering if the page had loaded at all. Switching to CSS Modules eliminated that problem entirely.

The Maintenance Maze of Dynamic Styles

CSS-in-JS promises easy dynamic styling based on props. In theory, it is elegant. In practice, for projects that need to scale over time, it becomes a tangled web of conditional logic.

Props-Driven Spaghetti

Consider this typical pattern:

const Button = styled.button`
  background: ${props => props.primary ? 'blue' : 'gray'};
  font-size: ${props => props.small ? '12px' : '16px'};
  margin: ${props => props.noMargin ? '0' : '10px'};
`;

This looks clean when you have three props. But after six months of development, with a dozen components and dozens of variants, you end up with functions inside your template literals that are harder to read than the actual business logic. Debugging becomes a nightmare because the CSS is hidden inside JavaScript, and you cannot use the browser's dev tools to inspect a class name—you have to trace the component tree.

The Croatian Team Problem

When working with a Croatian team of developers, not everyone is a front-end specialist. You might have a backend developer jumping in to fix a button style or a designer who needs to tweak a color. With CSS-in-JS, they have to understand React props, component logic, and template literals just to change a background color. With a simple .button.css file, anyone can open it, find the class, and make a change. That accessibility matters for long-term maintenance and team velocity.

The Tooling and Debugging Disconnect

This might be the most practical reason for my shift. The browser's native developer tools are built for CSS. They are not built for JavaScript-generated styles.

Lost in the Inspector

When you inspect an element styled with CSS-in-JS, you see a cryptic class name like sc-bdVaJa. Clicking on it in the styles panel shows you the generated CSS, but it is detached from your source code. You cannot easily click to open the file in your editor. You cannot see the original template literal. You are looking at a compiled, minified version of your intent.

In contrast, with CSS Modules or plain CSS, the class name maps directly to your source file. You can see the exact rule, the exact file, and the exact line number. For a project where I am the sole developer or working with a small Croatian agency, this debugging efficiency is not a luxury—it is a necessity. It saves me hours of frustration over the lifecycle of a project.

The Source Map Headache

Some CSS-in-JS libraries offer source maps, but they are rarely perfect. They add more build complexity and often break with newer bundler versions. I remember spending an entire afternoon trying to get source maps working for a client's project in Vite. It turned out to be a version mismatch with a Babel plugin. That afternoon was wasted on tooling, not on building features. When I switched to vanilla CSS, the problem disappeared.

A Concrete Example: The Croatian Travel Portal

Let me share a brief anecdote. I was rebuilding a small travel portal for a Croatian island tourism board. The site needed to be fast, mobile-first, and easy for a local administrator to update. The old site used Bootstrap and was a mess of conflicting styles.

I initially built the new version with styled-components. It was clean and componentized. But when I tested it on a real 3G connection using Chrome DevTools throttling, the first paint was over 2 seconds. The critical CSS was being injected late because it was part of the JavaScript bundle.

I spent a weekend rewriting the entire project using CSS Modules and a simple utility-first approach with a tiny custom framework. No runtime, no JavaScript for styles. The first paint dropped to under 0.8 seconds. The page was interactive in under 1.5 seconds. The codebase was also simpler. The local administrator could open the CSS file, find the .hero-title class, and change the font size without touching a single line of React code.

That project sealed the deal for me. The performance gain was not marginal—it was transformative for the end users.

What I Use Now: A Pragmatic Stack for Croatian Projects

I did not abandon all CSS-in-JS principles. I still believe in scoped styles and component isolation. I just choose tools that achieve that without the runtime overhead.

CSS Modules with a Build Step

My default now is CSS Modules, usually paired with a build tool like Vite or Webpack. You write plain CSS files, import them into your components, and get scoped class names automatically. No runtime cost, no JavaScript execution for styles. The browser loads a small, compressed CSS file and renders immediately.

For dynamic styles, I use inline styles or conditional class names. It is less "magical" than props-driven styled components, but it is far more predictable and debuggable.

/* Button.module.css */
.primary {
  background: blue;
  color: white;
}
.small {
  font-size: 12px;
}
// Button.jsx
import styles from './Button.module.css';

function Button({ primary, small }) {
  return (
    <button className={`${styles.button} ${primary ? styles.primary : ''} ${small ? styles.small : ''}`}>
      Click me
    </button>
  );
}

This pattern is simple, explicit, and performs exactly like plain CSS. It works perfectly for Croatian projects where the target audience values speed and reliability over developer novelty.

Utility-First CSS (Like Tailwind) as a Compromise

For larger projects with a strong design system, I have also found utility-first CSS frameworks like Tailwind CSS to be an excellent middle ground. They provide a consistent set of classes, avoid the specificity wars of traditional CSS, and produce incredibly small production bundles when purged. Tailwind compiles to static CSS. There is no runtime. It is fast, predictable, and any developer on your team can read it.

The Practical Takeaway: Think About the Last Mile

If you are building for a Croatian audience, or any audience that is not sitting on a fiber connection in a tech hub, you need to think about the last mile. Your users are on phones, on trains, on slow connections, and they want your site to load now. CSS-in-JS adds a barrier between your code and that user's screen.

My advice is simple: before you reach for a CSS-in-JS library for your next project, ask yourself if the dynamic styling it provides is truly worth the runtime cost. For most sites, it is not. Start with CSS Modules or a utility-first framework. Your users will thank you with faster load times, and your future self will thank you with a codebase that is easier to debug and maintain.

The best tool is the one that gets out of the way. For me, plain CSS—with a little help from modern build tools—does exactly that. Give your users the fastest possible experience, not the most fashionable developer workflow.