In this article, we will look at EffCSS and how to use it in your project.
Main links
Overview
EffCSS is a self-confident CSS-in-JS library which aims to be a simple tool for creating complex and predictable styles. The library is based on several abstractions:
-
Stylesheet makeris just a JS function that receive an object with utilities as an argument and should return an object or a string with styles; utilities contain some usefull functions that generates pseudo selectors, at-rules, BEM selectors etc., -
Style provideris a custom HTMLScriptElement which process and manage stylesheet makers.
It uses common browser APIs such as
As a result, EffCSS does not require any external dependencies. It works the same way in all modern browsers and with any frameworks. You can try it with StackBlitz - there are a lot of example projects in the collection. Is that enough to interest you?
In fact, there may be several other reasons to try EffCSS:
You want to
- use a framework-agnostic tool (without "integrations", really agnostic),
- use styles multiple times with a TypeScript hints and full JavaScript power,
- use styles on the server side without additional settings.
You don't like to
- install additional build plugins in a project,
- add special extensions to your editor,
- learn new syntax.
Did you like at least one? Then let's getting started!
Install
Type in your terminal:
# npm
npm i effcss
# pnpm
pnpm add effcss
# yarn
yarn add effcss
Use
CSS stylesheet can be created by StyleSheet maker function. Let's write it:
import type { TStyleSheetMaker } from 'effcss';
export const maker: TStyleSheetMaker = () => {
const flexColumn = {
display: 'flex',
flexDirection: 'column',
};
return {
html: {
fontSize: '16px',
},
'.card': {
width: '10rem',
...flexColumn,
'.card__header': {
height: '2rem',
},
'.card__body': flexColumn,
':hover': {
borderRadius: '1rem',
},
},
};
};
Please note that CSS property names can be described in camelCase style. And your function can use external values if there is a need.
Then use Style provider to apply this stylesheet to the page using:
import { useStyleProvider } from 'effcss';
import { maker } from './maker';
const provider = useStyleProvider();
provider.use(maker);
Simple, isn't it?
More complex example
First, we'll connect the provider with the selector minification parameter and pass it down the component tree:
main.js
import { useStyleProvider } from "effcss";
const consumer = useStyleProvider({
attrs: {
min: true // to create minified selectors
}
});
const root = createRoot(document.getElementById("root"));
root.render(<App css={consumer} />);
Then we'll describe the stylesheet using a type, implement it using maker function, and connect it using provider:
App.tsx
import { useRef } from 'react';
import { IStyleProvider, TStyleSheetMaker } from 'effcss';
// you can describe your styles using BEM notation
// so that other people can use them via TypeScript generics
export type TCardMaker = {
/**
* Card block
*/
card: {
/**
* Card modifiers
*/
'': {
/**
* Card border radius
*/
rounded: '';
/**
* Card height
*/
h: 'full' | 'half';
};
/**
* Card logo
*/
logo: {
/**
* Logo width
*/
w: 's' | 'l';
},
/**
* Card footer
*/
footer: {
/**
* Footer visibility
*/
visible: '';
/**
* Footer size
*/
sz: 's' | 'm' | 'l';
};
};
}
const myStyleSheetMaker: TStyleSheetMaker = ({ bem, pseudo, at: { keyframes }, merge, palette, coef, size, units: {px} }) = {
// specify selector variants via generic
const selector = bem<TCardMaker>;
// create property with unique identifier
const widthProperty = property({
ini: px(200),
inh: false,
def: px(200) // will be used as fallback value in `var()` expression
});
// create keyframes with unique identifier
const spin = keyframes({
from: {
transform: 'rotate(0deg)',
},
to: {
transform: 'rotate(360deg)',
},
});
// deeply merge objects
const cardLogoStyles = merge({
width: widthProperty,
animation: `20s linear infinite ${spin}`,
[pseudo.h()]: {
filter: "drop-shadow(0 0 2em #61dafbaa)",
}
}, {
border: 'none',
background: palette.pale.xl.alpha(0.8),
aspectRatio: 1,
[pseudo.h()]: {
opacity: 0.5
}
});
return {
...sizeProperty,
...spin,
[selector('card')]: {
// put card styles here
},
[selector('card.logo')]: cardLogoStyles,
[selector('card.logo.w.s')]: {
...widthProperty(px(100))
},
[selector('card.logo.w.l')]: widthProperty(px(300)),
[selector('card..rounded')]: {},
[selector('card..h.full')]: {},
[selector('card.footer')]: {},
[selector('card.footer.visible')]: {},
...each(coef.short, (k, v) => ({
[selector(`card.footer.sz.${k}`)]: {
height: size(v)
}
}))
};
};
export const App = (props: {
css: IStyleProvider;
}) => {
const { css } = props;
const stylesRef = useRef();
if (!stylesRef.current) {
const [card] = css.use(myStyleSheetMaker)<TCardMaker>;
// thanks to the TCardMaker type,
// you don't need to look at the implementation - just create the necessary attributes
stylesRef.current = {
card: card('card..rounded'),
// element with modifiers
footer: card({
card: {
footer: {
visible: '',
size: 'm'
}
}
})
};
}
const styles = stylesRef.current;
// just apply attributes to appropriate elements
return (
<div {...styles.card}>
<div {...styles.footer}>...</div>
</div>
);
};
As you can see, the selectors are output by EffCSS automatically - you don't need to memorize them, just pass the necessary parameters to the resolver.
This is the whole point of EffCSS - styles can be described as independent resources and applied to any layout.
Explore and use it to the fullest
In fact, there are many more possibilities. To save you time, here are the links to the documentation:
I will be glad if this tool is useful to you. Enjoy your frontend development!
Top comments (0)