CSS-in-TS - a way to improve Development Experience

Published: (December 28, 2025 at 01:41 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Cover image for CSS‑in‑TS – a way to improve Development Experience

Marat Sabitov

TypeScript is a modern web‑development standard. It is an “armor” over JavaScript that protects you from typos and mistakes (think Iron Man’s armor – you feel in total control).

But when you step into the world of CSS it becomes uncomfortable – you have to look at the implementation of the rules, the names of the selectors, and you can only use a CSS file if you already know its contents. Moreover, CSS syntax is limited and quite verbose.

As a frontend developer I want to use all the features of JavaScript and TypeScript when writing styles – that’s why I love CSS‑in‑JS. Yet I couldn’t find a CSS‑in‑TS library that suited me, so I created my own.

Introducing EffCSS

EffCSS suggests first describing the desired result as a TypeScript type, then implementing it. The type acts as a contract between the people who create styles and those who use them. Even before you start coding you can see which rules are really needed and avoid writing unnecessary ones.

Example: product‑card stylesheet

export type TProductCard = {
  /**
   * Width utility (use theme vars)
   */
  w: "s" | "m" | "l";

  /**
   * Product card
   */
  card: {
    /**
     * Card header
     */
    header: {
      /**
       * Background color
       */
      bg: "primary" | "secondary";

      /**
       * Font‑weight
       */
      fw: "bold" | "light";

      /**
       * Caption
       */
      caption: {
        /**
         * Caption position
         */
        pos: "l" | "r" | "c";

        /**
         * Is caption hidden
         */
        hidden: "";
      };
    };
    footer: Record;
  };

  /**
   * Product preview
   */
  preview: {
    avatar: {
      /**
       * Border‑radius (rem)
       */
      rad: 0 | 1 | 2;
    };
    caption: Record;
  };
};

This type fully describes the public API of the stylesheet.

In EffCSS each stylesheet is created with a stylesheet maker – a plain JavaScript function that returns an object with styles.

import { TStyleSheetMaker } from "effcss";

export type TProductCard = { /* …as above… */ };

const themeWidth = {
  s: "160px",
  m: "320px",
  l: "480px",
} as const;

export const productCard: TStyleSheetMaker = ({ select, each }) => {
  // `select` is typed, so the selector is checked against the contract
  const selector = select;

  return {
    // generate a rule for each width option
    ...each(themeWidth, (key, val) => ({
      [selector(`w:${key}`)]: { width: val },
    })),

    [selector("card")]: {
      /* card styles */
    },

    [selector("card.header")]: {
      /* card header styles */
    },

    [selector("card.header.bg:primary")]: {
      /* card header primary background */
    },

    [selector("card.header.bg:secondary")]: {
      /* card header secondary background */
    },

    [selector("card.header.caption.hidden:")]: {
      /* styles to hide caption */
    },

    // …and everything else you need
  };
};

Why not write selectors manually?

  • Minification – Long, meaningful names are great for development but make the final HTML/CSS heavier. EffCSS can compress selectors automatically.
  • Generation mode – Selectors can be CSS classes or data attributes depending on the build mode.
  • Uniqueness – Each stylesheet gets its own unique prefix, applied inside select, so you never have to worry about collisions.

How to use the styles

Assume a colleague is working with React and needs your styles. The workflow is straightforward:

  1. Create a style provider (optionally with global attributes).
  2. Register the stylesheet maker with use.
  3. Resolve selectors either as a list of class names or as an object.
import { useStyleProvider } from "effcss";
import { productCard } from "./productCard";
import type { TProductCard } from "./productCard";

// 1️⃣ Create a style provider
const styleProvider = useStyleProvider({
  attrs: { min: true }, // e.g., enable minified output
});

// 2️⃣ Register the stylesheet maker
const [resolve] = styleProvider.use(productCard);

// 3️⃣ Resolve selectors
const styles = {
  // as a list of class names (or data‑attributes)
  card: resolve.list("w:m", "card"),

  // as an object that mirrors the type structure
  header: resolve.obj({
    card: {
      header: {
        bg: "primary",
        fw: "bold",
      },
    },
  }),
};

Now styles.card contains the generated selector(s) for a medium‑width card, and styles.header gives you the exact selector(s) for a card header with a primary background and bold font‑weight. Use these values in your JSX just like any other class name or data attribute.


That’s it! With EffCSS you get type‑safe, minifiable, and uniquely‑prefixed selectors while keeping the full power of TypeScript at your fingertips. Happy styling!

export const styles = {
    card: css({
        // …
    }),
    header: css({
        // …
    }),
};

export function Component() {
    // and just apply it
    return (
        <>
            Hello, product card!
            {/* … */}
        </>
    );
}

Fullscreen Controls

Enter fullscreen mode
Exit fullscreen mode

Final thoughts

I think separating implementation and usage is exactly what TypeScript is so good at. Therefore, CSS‑in‑TS is an ideal way to structure styles, isolate, and reuse them.

I will be glad if the article was helpful. Here are a couple more interesting links:

Enjoy your frontend development!

Back to Blog

Related posts

Read more »

Developer? Or Just a Toolor?

! https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%...

The Invisible Hand That Fed Us

Tailwind’s Layoffs and the New Reality A few days ago, I learned that Tailwind laid off 75 % of their workforce. Their framework is everywhere now—every fronte...