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 uses TypeScript generics, hiding the implementation of styles behind the external interface.

Description of styles

The first step in developing styles using EffCSS is to capture the provided functionality as a TypeScript type. Since version v4.6.0, there is no limit to the nesting levels of an object, and at any level you can define properties with a string or numeric value:

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

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

Implementation of styles

The second step in developing styles using EffCSS is to implement the described functionality within the framework of the StyleSheet maker function. To generate unique selectors with the style sheet prefix in EffCSS, the 'select` utility is used. This utility also checks the validity of implemented CSS selectors.

Each selector is formed by connecting using a path point to the desired property and adding a sign : before its value, if it is not an object:

ts
import { TStyleSheetMaker } from 'effcss';

export type TCustomMaker = {...};

const maker: TStyleSheetMaker = ({
  select,
}) => {
    const selector = select<TCustomMaker>;
    return {
        [selector('logo')]: {
            // ...
        },
        [selector('logo.scale:1')]: {
            // ...
        },
        [selector('logo.spin:')]: {
            // ...
        },
        [selector('panel.closeBtn')]: {
            // ...
        }
    }
};

export default maker;

Usage of styles

To get the selector resolver, you need to pass the corresponding StyleSheet maker function to the use method of the Style provider object. The resolver has two methods, list and obj, and they are used to output HTML attributes with the necessary selectors:

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

const getStyle = (use: IStyleProvider['use']) => {
    const [css] = use(customStyle);
    return {
        firstElemAttrs: css.obj<TCustomMaker>({
            w: 's',
            panel: {
                max: 'parent'
            }
        }), // {[attributeName]: 'selectors_string'}
        secondElemAttrs: css.list<TCustomMaker>('logo.spin:', 'body.p:m') // {[attributeName]: 'selectors_string'}
    };
};

Attribute generation modes

The Style provider component can influence the generation of 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.

If the a generation mode is used and the framework used supports this syntax, the resulting attributes can be passed directly to the layout:

tsx
<div {...firstElemAttrs}>...</div>

If the c generation mode is used, you can get the classes from the object and pass them to the layout:

tsx
<div className={firstElemAttrs.class}>...</div>

Explicit generation of classnames

Starting from version 4.10.0, you can directly get the CSS class selectors you need using the cx utility. This utility ignores mode attribute of Style provider and returns a string with classNames:

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

const getStyle = (provider: IStyleProvider) => {
    return {
        firstElemAttrs: provider.cx<TCustomMaker>(customStyle, {
            w: 's',
            panel: {
                max: 'parent'
            }
        }),
        secondElemAttrs: provider.cx<TCustomMaker>(customStyle, ['logo.spin:', 'body.p:m'])
    };
};

Explicit generation of data attributes

Starting from version 4.10.0, you can directly get the CSS data attribute selectors you need using the dx utility. This utility ignores mode attribute of Style provider and returns a object with data attributes:

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

const getStyle = (provider: IStyleProvider) => {
    return {
        firstElemAttrs: provider.dx<TCustomMaker>(customStyle, {
            w: 's',
            panel: {
                max: 'parent'
            }
        }),
        secondElemAttrs: provider.dx<TCustomMaker>(customStyle, ['logo.spin:', 'body.p:m'])
    };
};

WARNING

Each StyleSheet maker creates only one stylesheet, so you need to use either the cx or dx utility with one maker, but not both.

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 selectors for each stylesheet, 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.