style9

CSS-in-JS compiler based on the ideas of Facebook’s stylex

Features

  • Compiles to atomic CSS
  • Typed styles using TypeScript
  • Converts font-size to REM¹
  • No selectors

Install

# Yarn
yarn add style9

# npm
npm install style9

Usage

import style9 from 'style9';

// Styles are created by calling style9.create
const styles = style9.create({
  blue: {
    color: 'blue',
  },
  red: {
    color: 'red'
  },
  boldYellow: {
    color: 'yellow',
    fontWeight: 'bold'
  }
});

// `styles` is now a function which can be called with the keys of the style
// object, and returns a string of class names.
// Ternary operator and logical AND is supported
document.body.className = styles('blue', isRed && 'red');

// Styles are merged like `Object.assign`, with later styles taking precedent
styles('blue', 'red') // will be red
styles('red', 'blue') // will be blue
styles('red', 'blue', 'boldYellow') // will be yellow and bold

// `styles` can also be called with an object of booleans. Later keys take precedent
styles({
  blue: isBlue,
  red: isRed
});

// To combine with external styles, style9 can be called with the style objects
// Styles are merged like above
style9(styles.blue, otherStyles.yellow);

// Styles have to be statically defined, but constants are supported
const RED = 'red';

const moreStyles = style9.create({
  red: {
    color: RED
  },
  margin: {
    // All properties are written in camelcase
    // Integers are converted to pixels where appropriate
    marginTop: 8
  },
  longhands: {
    // Because of how classnames are resolved,
    // shorthand CSS properties are not supported
    // background: 'red' // INVALID
    backgroundColor: 'red', // OK
    // Some shorthand properties can be automatically expanded
    borderColor: 'blue' // will make all sides blue
    // See Style.d.ts for a complete list of supported CSS properties
  },
  padding: {
    // Longhands take precedent over shorthands
    // Will resolve to '12px 12px 12px 18px'
    paddingLeft: 18,
    padding: 12
    // Shorthand values will be copied to longhands which means
    // `padding: '12px 18px'` etc. is not supported
  },
  text: {
    // Font size is converted to REMs to follow users settings
    fontSize: 14
  },
  fadeIn: {
    // Animation names are created by calling style9.keyframes
    animationName: style9.keyframes({
      from: {
        opacity: 0
      },
      to: {
        opacity: 1
      }
    }),
    animationDuration: '1s'
  },
  mobile: {
    // Media queries are supported as well
    // They will be sorted mobile-first
    '@media (min-width: 800px)': {
      display: 'none'
    },
    // NOTE: For TypeScript < 4.3 the following syntax is required for media queries
    '@media': {
      '(min-width: 800px)': {
        display: 'none'
      }
    }
  },
  pseudo: {
    // Pseudo-classes and elements can be used
    ':hover': {
      // They can be nested, as can media queries
      ':active': {
        '::before': {
          content: 'attr(title)'
        }
      }
    }
  }
});

Options

  • minifyProperties - minify the property names of style objects. Unless you pass custom objects to style9() not generated by style9.create(), this is safe to enable and will lead to smaller JavaScript output but mangled property names. Consider enabling it in production. Default: false

Babel

const babel = require('@babel/core');

const output = babel.transformFile('./file.js', {
  plugins: [['style9/babel', { /* options */ }]]
});
// Generated CSS
console.log(output.metadata.style9);

Rollup

import style9 from 'style9/rollup';

export default {
  // ...
  plugins: [
    style9({
      // include & exclude are supported according to rollup conventions
      // Either fileName or name is required. It will be passed to emitFile
      // fileName: unique name to for output file
      // name: name to use for output.assetFileNames pattern
      // parserOptions: options to pass to the Babel parser
      // ...Options
    })
  ]
};

Webpack

const Style9Plugin = require('style9/webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.(tsx|ts|js|mjs|jsx)$/,
        use: Style9Plugin.loader,
        options: {
          // parserOptions: options to pass to the Babel parser
          // ...Options
        }
      },
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new Style9Plugin(),
    new MiniCssExtractPlugin()
  ]
};

Next.js

const withTM = require('next-transpile-modules')(['style9']);
const withStyle9 = require('style9/next');

module.exports = withStyle9({
  // parserOptions: options to pass to the Babel parser
  // ...Options
})(withTM());

Gatsby

module.exports = {
  plugins: [
    {
      resolve: 'style9/gatsby',
      options: {
        // parserOptions: options to pass to the Babel parser
        // ...Options
      }
    }
  ]
}

Vue.js

There is an example repo you can see to get started.

CSS Custom Properties with TypeScript

By augmenting CustomProperties, you can get type safety and autocompletion both when defining custom properties and when using them.

declare module 'style9/Style' {
  interface CustomProperties {
    '--bg-color'?: string;
    '--font-size'?: 18 | 24;
  }
}

style9.create({
  styles: {
    '--bg-color': 'blue',
    backgroundColor: 'var(--bg-color)'
  }
});