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
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:
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>;
}
};
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:
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
};
};
When you describe each selector using such a call, a legitimate question arises
Why not just create selectors yourself?
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?
How to use styles
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:
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>
};
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.
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 helpfulg. Here are a couple more interesting links
Enjoy your frontend development!
Top comments (0)