Skip to content

CSS-in-TS

The StyleSheet maker functions can describe the generated styles using special types, so when using them, TypeScript will prompt the available CSS selectors. This feature combines the methodology of BEM and TypeScript generics, hiding the implementation of styles behind the external interface.

Description of styles

The first step in developing styles using EffCSS is to declare the functionality provided as a type that describes your styles at three levels of the BEM methodology

  • blocks (1st level),
  • elements (2nd level),
  • modifiers (3rd level).

For all levels, it is recommended to use meaningful names written in the lowerCamelCase style.

WARNING

Do not use dots and underscores in names, as these characters are used to output selectors and attributes of EffCSS

INFO

You don't have to use all three levels. If you don't want to describe styles in terms of BEM, just describe your selectors at the level of individual blocks.

ts
export type TCustomMaker = {
    /**
     * Default block
     */
    '': {
        /**
         * Logo element
         */
        logo: {
            /**
             * Spin animation
             */
            spin: '';
            /**
             * Logo scale
             */
            scale: 1 | 2;
        };
        /**
         * Body element
         */
        body: {
            /**
             * Padding size
             */
            p: 's' | 'm' | 'l';
        };
        /**
         * Footer element
         */
        footer: {
            /**
             * Is footer hidden
             */
            hidden: '';
        };
    };
    /**
     * Panel block
     */
    panel: {
        /**
         * Default block
         */
        '': {
            /**
             * Is panel modal
             */
            modal: '';
            /**
             * Max panel width
             */
            max: 'vw' | 'parent';
        };
        /**
         * Close button block
         */
        closeBtn: {};
    }
};

The block does not have to contain elements. The element does not have to contain modifiers.

Note that the block modifiers are also described at level 3 inside the element with the key "". The block with the "" key is considered the default or primary block. There may also be a situation where styles describe some utilities that are not tied to a specific block and/or element (as in Tailwind) - then the following description comes to the rescue

ts
export type TCustomMaker = {
    '': {
        '': {
            /**
             * Width 
             */
            w: 's' | 'm' | 'l';
            /**
             * Height 
             */
            h: 's' | 'm' | 'l';
        }
    };
};

Implementation of styles

The second step in developing styles using EffCSS is to implement the described functionality within the StyleSheet maker function. The bem utility is used in EffCSS to generate BEM selectors. This utility also checks the validity of implemented CSS selectors, since it is a generic.

Each selector is formed by connecting the names of the block, element, modifier, and modifier value (if it is not Boolean) using a dot.

ts
import { TStyleSheetMaker } from 'effcss';

export type TCustomMaker = {...};

const maker: TStyleSheetMaker = ({
  bem,
}) => {
  return {
    // the default block selector
    [bem<TCustomMaker>('')]: {...},
    // selector for an element inside the default block
    [bem<TCustomMaker>('.logo')]: {...},
    // element boolean modifier selector
    [bem<TCustomMaker>('.logo.spin')]: {}
    // selector for a block modifier with a specific value
    [bem<TCustomMaker>('panel..max.vw')]: {}
  }
};

export default maker;

Usage of styles

Ready-made EffCSS styles can be used by passing the corresponding StyleSheet maker function to the use method of the Style consumer object. The usage is similar to the bem utility - the entered arguments will also be checked for compliance with the TCustomMaker type.

ts
import { IStyleProvider } from 'effcss';
import { default as customStyle, TCustomMaker } from './styles/custom';

const getStyle = (use: IStyleProvider['use']) => {
    // EffCSS v3 returns a resolver function,
    // and EffCSS v4 returns an array of resolvers
    const [css] = use(customStyle);
    return {
        block: css<TCustomMaker>(''),
        logo: css<TCustomMaker>('.logo'),
        spinLogo: css<TCustomMaker>('.logo.spin'),
        footer: css<TCustomMaker>('.footer'),
        body: css<TCustomMaker>('.body.p.m'),
    };
};

You can also pass an object to generate multiple selectors at once

ts
import { IStyleProvider } from 'effcss';
import { default as customStyle, TCustomMaker } from './styles/custom';

const getStyle = (use: IStyleProvider['use']) => {
    // EffCSS v3 returns a resolver function,
    // and EffCSS v4 returns an array of resolvers
    const [css] = use(customStyle);
    return css<TCustomMaker>({
        '': {
            logo: {
                spin: '',
                scale: 2
            };
        }
    });
};

Or you can pass an array of strings

ts
import { IStyleProvider } from 'effcss';
import { default as customStyle, TCustomMaker } from './styles/custom';

const getStyle = (use: IStyleProvider['use']) => {
    // EffCSS v3 returns a resolver function,
    // and EffCSS v4 returns an array of resolvers
    const [css] = use(customStyle);
    return css<TCustomMaker>(['.logo.spin', '.logo.scale.2']);
};

Alternatively, you can get the MonoResolver object first, and then refine it as needed

ts
import { IStyleProvider } from 'effcss';
import { default as customStyle, TCustomMaker } from './styles/custom';

const getStyle = (use: IStyleProvider['use']) => {
    // EffCSS v3 returns a resolver function,
    // and EffCSS v4 returns an array of resolvers
    const [css] = use(customStyle);
    const block = css<TCustomMaker>();
    const logo = block.e('logo');
    const logoSpin = logo.m({
        spin: ''
    });
    return {
        block: block.$,
        logo: logo.$,
        spinLogo: logoSpin.$,
        footer: block.e('footer').$,
        body: block.e('body').m({
            p: 'm'
        }).$,
  };
};

Use the way you like the most.

Attribute generation modes

The Style provider component can influence the generation of BEM selectors using 2 attributes:

  • mode - can have the value a (uses the data-* attributes) and c (uses the class attribute),
  • min is a Boolean attribute that allows you to minify BEM selectors.

CSS class generation is more predictable, and data attribute generation allows you to visually separate different style sheets. Use what you like best.

Minification retains a unique prefix in the selector, so the styles of different tables will remain isolated, while the length of the selectors will be significantly reduced.

Benefit

The described approach allows you to separate the contract from the implementation. During development, you initially know which CSS selectors should be provided, and any third-party developer can import your TCustomMaker type and get the necessary styles without looking into your `StyleSheet maker' function. Considering that JS files are usually minified during assembly, the proposed approach saves a lot of time.

In addition, EffCSS generates its own unique BEM selectors for each style sheet, which eliminates the risk of redefining them.

Thus, EffCSS allows you to step beyond the usual creation of CSS, approaching the role of CSS-in-TS.

Released under the Apache-2.0 License.