Skip to content

Guide

In this section, we'll install EffCSS and see how to use it.

Install

Type in your terminal:

sh
# npm
npm i effcss

# pnpm
pnpm add effcss

# yarn
yarn add effcss

Use

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:

ts
/**
 * 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:

tsx
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:

tsx
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:

tsx
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:

tsx
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:

tsx
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:

tsx
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:

tsx
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:

tsx
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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
const custom = customStyles(() => {
    return {
        '.custom': {
            padding: '1rem'
        },
    };
});
// specified stylesheet
const customStylesheet = stylesheet(custom);

There are also utilities that return special stylesheets:

  • layersStylesheet returns a stylesheet for global layers,
  • variablesStylesheet returns a stylesheet for global variables,
  • animationsStylesheet returns a stylesheet for global animations,
  • fontsStylesheet returns a stylesheet for global fonts,
  • sharedStylesheet returns a stylesheet of global rules (created using className and attribute).

configure

configure affects style generation if it is called before the first stylesheet is created:

ts
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:

ts
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:

ts
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.

Released under the Apache-2.0 License.