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:
[name]
- The base name of the file, without the extension.[hash]
- A hash of the full file path.[content-hash]
- A hash of the file contents.[local]
- The original class name or identifier.
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.
- Non-function syntax for the
:local
and:global
pseudo classes. - The
@value
rule – superseded by standard CSS variables. - The
:import
and:export
ICSS rules.