DEV Community

Cover image for EffCSS. Quick start
Marat Sabitov
Marat Sabitov

Posted on

EffCSS. Quick start

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 maker is 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 provider is 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

Enter fullscreen mode Exit fullscreen mode

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',
            },
        },
    };
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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} />);
Enter fullscreen mode Exit fullscreen mode

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>
    );
};
Enter fullscreen mode Exit fullscreen mode

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)