Lightning CSS

PlaygroundDocsRust docsnpmGitHub

CSS modules

By default, CSS identifiers are global. If two files define the same class names, ids, custom properties, @keyframes, etc., they will potentially clash and overwrite each other. To solve this, Lightning CSS supports CSS modules.

CSS modules treat the classes defined in each file as unique. Each class name or identifier is renamed to include a unique hash, and a mapping is exported to JavaScript to allow referencing them.

To enable CSS modules, provide the cssModules option when calling the Lightning CSS API. When using the CLI, enable the --css-modules flag.

import {transform} from 'lightningcss';

let {code, map, exports} = transform({
  // ...
  cssModules: true,
  code: Buffer.from(`
    .logo {
      background: skyblue;
    }
  `),
});

This returns an exports object in addition to the compiled code and source map. Each property in the exports object maps from the original name in the source CSS to the compiled (i.e. hashed) name. You can use this mapping in your JavaScript or template files to reference the compiled classes and identifiers.

The exports object for the above example might look like this:

{
  logo: {
    name: '8h19c6_logo',
    isReferenced: false,
    composes: []
  }
}

Class composition

Style rules in CSS modules can reference other classes with the composes property. This causes the referenced class to be applied whenever the composed class is used, effectively providing a form of style mixins.

.bg-indigo {
  background: indigo;
}

.indigo-white {
  composes: bg-indigo;
  color: white;
}

In the above example, whenever the indigo-white class is applied, the bg-indigo class will be applied as well. This is indicated in the exports object returned by Lightning CSS as follows:

{
  'bg-indigo': {
    name: '8h19c6_bg-indigo',
    isReferenced: true,
    composes: []
  },
  'indigo-white': {
    name: '8h19c6_indigo-white',
    isReferenced: false,
    composes: [{
      type: 'local',
      name: '8h19c6_bg-indigo'
    }]
  }
}

Multiple classes can be composed at once by separating them with spaces.

.logo {
  composes: bg-indigo padding-large;
}

Dependencies

You can also reference class names defined in a different CSS file using the from keyword:

.logo {
  composes: bg-indigo from './colors.module.css';
}

This outputs an exports object with the dependency information. It is the caller's responsibility to resolve this dependency and apply the target class name when using the transform API. When using the bundle API, this is handled automatically.

{
  logo: {
    name: '8h19c6_logo',
    isReferenced: false,
    composes: [{
      type: 'dependency',
      name: 'bg-indigo',
      specifier: './colors.module.css'
    }]
  }
}

Global composition

Global (i.e. non-hashed) classes can also be composed using the global keyword:

.search {
  composes: search-widget from global;
}

Global exceptions

Within a CSS module, all class and id selectors are local by default. You can also opt out of this behavior for a single selector using the :global pseudo class.

.foo :global(.bar) {
  color: red;
}

.foo .bar {
  color: green;
}

compiles to:

.EgL3uq_foo .bar {
  color: red;
}

.EgL3uq_foo .EgL3uq_bar {
  color: #ff0;
}

Local CSS variables

By default, class names, id selectors, and the names of @keyframes, @counter-style, and CSS grid lines and areas are scoped to the module they are defined in. Scoping for CSS variables and other <dashed-ident> names can also be enabled using the dashedIdents option when calling the Lightning CSS API. When using the CLI, enable the --css-modules-dashed-idents flag.

let {code, map, exports} = transform({
  // ...
  cssModules: {
    dashedIdents: true,
  },
});

When enabled, CSS variables will be renamed so they don't conflict with variable names defined in other files. Referencing a variable uses the standard var() syntax, which Lightning CSS will update to match the locally scoped variable name.

:root {
  --accent-color: hotpink;
}

.button {
  background: var(--accent-color);
}

becomes:

:root {
  --EgL3uq_accent-color: hotpink;
}

.EgL3uq_button {
  background: var(--EgL3uq_accent-color);
}

You can also reference variables defined in other files using the from keyword:

.button {
  background: var(--accent-color from './vars.module.css');
}

Global variables may be referenced using the from global syntax.

.button {
  color: var(--color from global);
}

The same syntax also applies to other CSS values that use the <dashed-ident> syntax. For example, the @font-palette-values rule and font-palette property use the <dashed-ident> syntax to define and refer to custom font color palettes, and will be scoped and referenced the same way as CSS variables.

Custom naming patterns

By default, Lightning CSS prepends the hash of the filename to each class name and identifier in a CSS file. You can configure this naming pattern using the pattern when calling the Lightning CSS API. When using the CLI, provide the --css-modules-pattern option.

A pattern is a string with placeholders that will be filled in by Lightning CSS. This allows you to add custom prefixes or adjust the naming convention for scoped classes.

let {code, map, exports} = transform({
  // ...
  cssModules: {
    pattern: 'my-company-[name]-[hash]-[local]',
  },
});

The following placeholders are currently supported:

CSS Grid

Note: CSS grid line names can be ambiguous due to automatic postfixing done by the browser, which generates line names ending with -start and -end for each grid template area. When using CSS grid, your "pattern" configuration must end with the [local] placeholder so that these references work correctly.

let { code, map, exports } = transform({
  // ...
  cssModules: {
    // ❌ [local] must be at the end so that
    // auto-generated grid line names work
    pattern: '[local]-[hash]'
    // ✅ do this instead
    pattern: '[hash]-[local]'
  }
});
.grid {
  grid-template-areas: 'nav main';
}

.nav {
  grid-column-start: nav-start;
}

Pure mode

Just like the pure option of the css-loader for webpack, Lightning CSS also has a pure option that enforces usage of one or more id or class selectors for each rule.

let {code, map, exports} = transform({
  // ...
  cssModules: {
    pure: true,
  },
});

If you enable this option, Lightning CSS will throw an error for CSS rules that don't have at least one id or class selector, like div. This is useful because selectors like div are not scoped and affects all elements on the page.

Turning off feature scoping

Scoping of grid, animations, and custom identifiers can be turned off. By default all of these are scoped.

let {code, map, exports} = transform({
  // ...
  cssModules: {
    animation: true,
    grid: true,
    customIdents: true,
  },
});

Unsupported features

Lightning CSS does not currently implement all CSS modules features available in other implementations. Some of these may be added in the future.