Guide
In this section, we'll install EffCSS and see how to use it.
Install
Type in your terminal:
# npm
npm i effcss
# pnpm
pnpm add effcss
# yarn
yarn add effcssUse
To create styles with EffCSS, you just need to call the utilities. Fortunately, there are very few, and by their name you can understand what they do.
Among all the utilities, you need to highlight classNames and attributes. They require specifying a contract in the form of a Typescript type with which selectors will be implemented. This will allow you to control both the creation of styles and their use. A contract type is an object with any level of nesting properties:
/**
* Components stylesheet
*/
type Components = {
/**
* Is rounded
*/
rounded: true;
/**
* Height
*/
h: 'full' | 'half';
/**
* Card
*/
card: {
/**
* Card background
*/
bg: 'primary' | 'secondary';
/**
* Is card disabled
*/
disabled: boolean;
};
/**
* Spinner component
*/
spinner: {};
};
/**
* Utils stylesheet
*/
type Utils = {
/**
* Width
*/
w: 's' | 'm' | 'l';
/**
* Spacing
*/
spacing: 0 | 1 | 2;
/**
* Blink animation
*/
blink: true;
};Other utilities derive types from their arguments. Let's take a closer look at the usage of each of them.
classNames
className creates a single CSS rule with the specified content and returns the class selector as a string:
import { className } from 'effcss';
// create
const cls = className({
margin: 'auto',
'&:hover': {
outline: '2px solid black',
'.child': {
background: 'grey'
}
}
});
// apply
export const Component = () => {
return <div className={cls}>
Card
</div>
};classNames creates a stylesheet and returns a function for deriving classnames:
import { classNames } from 'effcss';
// declare
type Card = {
w: 's' | 'm' | 'l';
blur: true;
card: {
variant: 1 | 2;
rounded: true;
};
}
// implement
const card = classNames<Card>((selectors) => {
const {w, card, blur} = selectors;
return {
[w.s]: {
width: '12px'
},
[w.m]: {
width: '24px'
},
[w.l]: {
width: '26px'
},
[blur.true]: {
filter: 'blur(5px)'
},
[card]: {
background: 'white',
border: 'none'
},
[card.variant[1]]: {
width: 'auto',
display: 'block',
padding: '12px',
'&:hover': {
cursor: 'pointer'
}
},
[card.variant[2]]: {
width: 'auto',
display: 'flex',
flexDirection: 'column',
padding: '16px',
'&:hover': {
outline: '2px solid black'
}
},
[card.rounded.true]: {
borderRadius: '1rem'
}
}
});
const cls = card({
card: {
rounded: true
},
w: 's'
});
// apply
export const Component = () => {
return <div className={cls}>
Card
</div>
};lazyClassNames differs from classNames in that it performs the passed function and creates a stylesheet after the selectors are derived for the first time:
import { lazyClassNames } from 'effcss';
// declare
type Card = {/* the same */};
// implement
const card = lazyClassNames<Card>(/* the same */);
// the stylesheet has not been created yet
const cls = card({
card: {
rounded: true
},
w: 's'
});
// the stylesheet has been created
// apply
export const Component = () => {
return <div className={cls}>
Card
</div>
};attributes
attribute creates a single CSS rule with the specified content and returns the attribute selector as an object:
import { attribute } from 'effcss';
// create
const attr = attribute({
margin: 'auto',
'&:hover': {
outline: '2px solid black',
'.child': {
background: 'grey'
}
}
});
// apply
export const Component = () => {
return <div {...attr}>
Card
</div>
};attributes creates stylesheet and returns a function for deriving attributes:
import { attributes } from 'effcss';
// declare
type Card = {
w: 's' | 'm' | 'l';
blur: true;
card: {
variant: 1 | 2;
rounded: true;
};
}
// implement
const card = attributes<Card>((selectors) => {
const {w, card, blur} = selectors;
return {
[w.s]: {
width: '12px'
},
[w.m]: {
width: '24px'
},
[w.l]: {
width: '26px'
},
[blur.true]: {
filter: 'blur(5px)'
},
[card]: {
background: 'white',
border: 'none'
},
[card.variant[1]]: {
width: 'auto',
display: 'block',
padding: '12px',
'&:hover': {
cursor: 'pointer'
}
},
[card.variant[2]]: {
width: 'auto',
display: 'flex',
flexDirection: 'column',
padding: '16px',
'&:hover': {
outline: '2px solid black'
}
},
[card.rounded.true]: {
borderRadius: '1rem'
}
}
});
const attrs = card({
card: {
rounded: true
},
w: 's'
});
// apply
export const Component = () => {
return <div {...attrs}>
Card
</div>
};lazyAttributes differs from attributes in that it performs the passed function and creates a stylesheet after the selectors are derived for the first time:
import { lazyAttributes } from 'effcss';
// declare
type Card = {/* the same */};
// implement
const card = lazyAttributes<Card>(/* the same */);
// the stylesheet has not been created yet
const attrs = card({
card: {
rounded: true
},
w: 's'
});
// the stylesheet has been created
// apply
export const Component = () => {
return <div {...attrs}>
Card
</div>
};customStyles
customStyles creates stylesheet without derived selectors:
import { customStyles } from 'effcss';
// implement
customStyles(() => ({
'.custom': {
background: 'transparent',
width: '100%',
'&:hover': {
outline: '2px solid black'
}
},
'@media screen and (max-width: 768px)': {
'.custom': {
width: '50%'
}
}
}));
// apply
export const Component = () => {
return <div className='custom'>
Card
</div>
};lazyCustomStyles differs from customStyles in that it performs the passed function and creates a stylesheet after the first result call:
import { lazyCustomStyles } from 'effcss';
// implement
const applyStyles = lazyCustomStyles(/* the same */);
// the stylesheet has not been created yet
applyStyles();
// the stylesheet has been created
// apply
export const Component = () => {
return <div className='custom'>
Card
</div>
};variables
variable creates a single CSS @property rule, variables creates several at once:
import { customStyles, variable, variables } from 'effcss';
// global
const offset = variable('10px');
const colors = variable({
primary: {
syntax: 'color',
inherits: false,
initialValue: '#2192a7'
},
secondary: '#425158'
});
customStyles(() => {
// local
const localOffset = variable({
inherits: true,
initialValue: '12px'
});
const localColors = variables({
primary: '#2192a7',
secondary: '#425158'
});
return {
'.global': {
background: colors.primary(),
// with fallback value
padding: offset('8px'),
},
'.local': {
// with fallback value
background: localColors.primary('grey'),
padding: localOffset()
},
'.override': {
[localColors.primary]: 'grey'
}
};
});You can get and set the initial value of a global variable using the corresponding methods:
const offset = variable('10px');
const colors = variable({
primary: {
syntax: 'color',
inherits: false,
initialValue: '#2192a7'
},
secondary: '#425158'
});
offset.set('14px');
colors.primary.set('grey');
colors.secondary.set('green');
const actualOffsetValue = offset.get();
const actualPrimaryColorValue = colors.primary.get();animations
animation creates a single CSS @keyframes rule, animations creates several at once:
import { customStyles, animation, animations } from 'effcss';
// global
const spin = animation({
from: {
transform: 'rotate(0deg)',
},
to: {
transform: 'rotate(360deg)',
},
});
const blink = animations({
simple: {
'50%': {
visibility: 'hidden'
}
},
smooth: {
'0%': {
opacity: 1
},
'50%': {
opacity: 0
},
'100%': {
opacity: 1
}
}
});
customStyles(() => {
// local
const localSpin = animation(/* the same */);
const localBlink = animations(/* the same */);
return {
'.global-spin': {
animation: `${spin} 6s infinite`
},
'.global-blink': {
animation: `${blink.smooth} 2s infinite`
},
'.local-spin': {
animation: `${localSpin} 6s infinite`
},
'.local-blink': {
animation: `${localBlink.smooth} 2s infinite`
},
};
});layers
layer creates a single CSS @layer rule, layers creates several at once:
import { customStyles, layer, layers } from 'effcss';
// global
const single = layer();
const list = layers(['theme', 'layout', 'utilities']);
customStyles(() => {
// local
const localSingle = layer();
const localList = layers(['theme', 'layout', 'utilities']);
return {
[single]: {
'.global-layer': {
background: 'transparent'
}
},
[list.theme]: {
'.global-layer': {
background: '#425158'
}
},
[localSingle]: {
'.local-layer': {
background: 'white'
}
},
[localList.theme]: {
'.local-layer': {
background: 'grey'
}
}
};
});containers
container creates a single CSS @container rule, containers creates several at once:
import { customStyles, container, containers } from 'effcss';
// global
const single = container();
const multiple = containers({
normal: '',
inline: 'inline-size',
scrollState: 'size scroll-state'
});
customStyles(() => {
// local
const localSingle = container();
const localMultiple = containers({
normal: '',
inline: 'inline-size',
scrollState: 'size scroll-state'
});
return {
'.global-container': {
container: single()
},
[single + ' not scroll-state(scrollable: none)']: {
'.inside-global-container': {
width: '100%'
}
},
'.local-container': {
container: localMultiple.inline()
},
[localMultiple.inline + ' (max-width: 768px)']: {
'.inside-local-container': {
width: '100%'
}
},
};
});fonts
font creates a single CSS @font-face rule, fonts creates several at once:
import { customStyles, font, fonts } from 'effcss';
// global
const single = font({
src: `url("https://mdn.github.io/shared-assets/fonts/FiraSans-Regular.woff2")`,
genericName: 'sans-serif'
});
const multiple = fonts({
primary: {
src: `url("/fonts/roboto-regular.woff2") format("woff2"), url("/fonts/roboto-regular.woff") format("woff")`,
weight: 400,
style: 'normal',
display: 'swap'
},
secondary: {
src: `url("https://mdn.github.io/shared-assets/fonts/FiraSans-Regular.woff2")`
}
});
customStyles(() => {
// local
const localSingle = font(/* the same */);
const localMultiple = fonts(/* the same */);
return {
'.global-font': {
fontFamily: single()
},
'.global-primary-font': {
// `single` as fallback
fontFamily: multiple.primary(single)
},
'.local': {
fontFamily: localSingle()
},
'.local-primary-font': {
// `localSingle` as fallback
fontFamily: localMultiple.primary(localSingle)
}
};
});update
update refreshes initial value of global variable/variables:
const offset = variable('10px');
const colors = variable({
primary: {
syntax: 'color',
inherits: false,
initialValue: '#2192a7'
},
secondary: '#425158'
});
update(offset, '14px');
update(colors, {
primary: 'grey',
secondary: 'green'
});Use the variable set method whenever possible, as it is more explicit.
stylesheet
stylesheet returns the created stylesheet:
const custom = customStyles(() => {
return {
'.custom': {
padding: '1rem'
},
};
});
// specified stylesheet
const customStylesheet = stylesheet(custom);There are also utilities that return special stylesheets:
layersStylesheetreturns a stylesheet for global layers,variablesStylesheetreturns a stylesheet for global variables,animationsStylesheetreturns a stylesheet for global animations,fontsStylesheetreturns a stylesheet for global fonts,sharedStylesheetreturns a stylesheet of global rules (created usingclassNameandattribute).
configure
configure affects style generation if it is called before the first stylesheet is created:
configure({
// custom prefix for ids
prefix: 'custom',
// disable minification
minify: false,
// emulate server-side mode
emulate: true
});serialize
serialize converts its argument or all created stylesheets to an HTML string:
const custom = customStyles(() => {
return {
'.custom': {
padding: '1rem'
},
};
});
// specified stylesheet
const customHTML = serialize(custom);
// all created stylesheets
const fullHTML = serialize();serializeMeta serializes the metadata of its argument or the metadata of all created stylesheets into an HTML string:
import { classNames } from 'effcss';
type Card = {/* the same */};
const card = classNames<Card>(/* the same */);
// specified stylesheet metadata
const cardMetaHTML = serializeMeta(card);
// all created stylesheets metadata
const fullMetaHTML = serialize();This way, styles and metadata can be calculated on the server and reused on the client. This is especially useful for SSG/SSR.