CSS-in-TS - a way to improve Development Experience
Source: Dev.to

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.
EffCSScan 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:
- Create a style provider (optionally with global attributes).
- Register the stylesheet maker with
use. - 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!
