Tools
CSS-in-TS - a way to improve Development Experience
2025-12-28
0 views
admin
EffCSS ## Why not just create selectors yourself? ## How to use styles ## Final thoughts TypeScript is a modern web development standard. This is an "armor" over JavaScript, which allows you not to be afraid of typos and mistakes (like the armor of Iron Man, because of the feeling of complete control). But when you enter the territory of CSS, it becomes uncomfortable - you have to look at the implementation of the rules, the names of the selectors. As a result, you can use a CSS file only if you know its contents. In addition, CSS syntax is very 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. But at the same time, I didn't find a CSS-in-TS library that was convenient for me, so I had to create my own. Let's welcome the EffCSS. EffCSS suggests first describing the desired result as a TypeScript type, and only then starting to implement it. This type is a contract between those who create styles and those who use them. Even before you start working, you can immediately evaluate which rules are really needed so as not to write unnecessary ones. For example, I was ordered product card styles: This type fully describes the public API of the stylesheet. In EffCSS, each stylesheet is created using a function called stylesheet maker. It is just a JS function which should return object with styles. I can implement it as follows: When you describe each selector using such a call, a legitimate question arises In short, because they need to be minified for production. Long and meaningful names are useful for development, but they make the final HTML and CSS heavier. EffCSS allows you to compress these selectors, so you don't have to think about it yourself. In addition, in EffCSS the type of selectors depends on the generation mode - they can be CSS classes or data attributes. Another reason is the uniqueness of selectors. In each stylesheet, EffCSS uses its own unique prefix and apply it inside select function, so it stops being your headache. Well, we've done our part. How are things on the other side? Let's say that your colleague writes on React. He knows that your styles are needed for his layout, how to apply them? It's very simple here - your colleague creates an instance of EffCSS Style provider and simply passes the stylesheet maker function to the use method. The resulting resolver gives you the ability to use a list or an object with the necessary selectors: As you can see, the developer does not need to look at your code if you implement the declared stylesheet type. Moreover, EffCSS is framework-agnostic, so your other colleague can reuse styles for example within Svelte components. 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 helpfulg. Here are a couple more interesting links Enjoy your frontend development! Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
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<string, never>; }; /** * Product preview */ preview: { avatar: { /** * Border-radius (rem) */ rad: 0 | 1 | 2; }; caption: Record<string, never>; }
}; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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<string, never>; }; /** * Product preview */ preview: { avatar: { /** * Border-radius (rem) */ rad: 0 | 1 | 2; }; caption: Record<string, never>; }
}; CODE_BLOCK:
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<string, never>; }; /** * Product preview */ preview: { avatar: { /** * Border-radius (rem) */ rad: 0 | 1 | 2; }; caption: Record<string, never>; }
}; COMMAND_BLOCK:
import { TStyleSheetMaker } from 'effcss'; export type TProductCard = {...}; const themeWidth = { s: '160px', m: '320px', l: '480px'
} as const; export const productCard: TStyleSheetMaker = ({ select, each
}) => { // we pass the type // so that the selector is checked for compliance with the contract const selector = select<TProductCard>; return { // we form rule for each 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:')]: { // some styles to hide caption }, // and everything like that };
}; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { TStyleSheetMaker } from 'effcss'; export type TProductCard = {...}; const themeWidth = { s: '160px', m: '320px', l: '480px'
} as const; export const productCard: TStyleSheetMaker = ({ select, each
}) => { // we pass the type // so that the selector is checked for compliance with the contract const selector = select<TProductCard>; return { // we form rule for each 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:')]: { // some styles to hide caption }, // and everything like that };
}; COMMAND_BLOCK:
import { TStyleSheetMaker } from 'effcss'; export type TProductCard = {...}; const themeWidth = { s: '160px', m: '320px', l: '480px'
} as const; export const productCard: TStyleSheetMaker = ({ select, each
}) => { // we pass the type // so that the selector is checked for compliance with the contract const selector = select<TProductCard>; return { // we form rule for each 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:')]: { // some styles to hide caption }, // and everything like that };
}; COMMAND_BLOCK:
import { useStyleProvider } from 'effcss';
import { productCard } from './productCard';
import type { TProductCard } from './productCard'; // create Style provider
const styleProvider = useStyleProvider({ attrs: { min: true }
}); // pass stylesheet maker
const [resolve] = styleProvider.use(productCard); // resolve selectors
const styles = { card: resolve.list<TProductCard>('w:m', 'card'), header: resolve.obj<TProductCard>({ card: { header: { bg: 'primary', fw: 'bold' } } }),
}; export function Component() { // and just apply it return <div {...styles.card}> <div {...styles.header}>Hello, product card!</div> <div>...</div> </div>
}; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useStyleProvider } from 'effcss';
import { productCard } from './productCard';
import type { TProductCard } from './productCard'; // create Style provider
const styleProvider = useStyleProvider({ attrs: { min: true }
}); // pass stylesheet maker
const [resolve] = styleProvider.use(productCard); // resolve selectors
const styles = { card: resolve.list<TProductCard>('w:m', 'card'), header: resolve.obj<TProductCard>({ card: { header: { bg: 'primary', fw: 'bold' } } }),
}; export function Component() { // and just apply it return <div {...styles.card}> <div {...styles.header}>Hello, product card!</div> <div>...</div> </div>
}; COMMAND_BLOCK:
import { useStyleProvider } from 'effcss';
import { productCard } from './productCard';
import type { TProductCard } from './productCard'; // create Style provider
const styleProvider = useStyleProvider({ attrs: { min: true }
}); // pass stylesheet maker
const [resolve] = styleProvider.use(productCard); // resolve selectors
const styles = { card: resolve.list<TProductCard>('w:m', 'card'), header: resolve.obj<TProductCard>({ card: { header: { bg: 'primary', fw: 'bold' } } }),
}; export function Component() { // and just apply it return <div {...styles.card}> <div {...styles.header}>Hello, product card!</div> <div>...</div> </div>
}; - EffCSS Docs
- SourceCraft
- StackBlitz examples collection
how-totutorialguidedev.toaimljavascriptgit