npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-css-layering

v0.3.0

Published

Wrap CSS in cascade layers for Vite.

Readme

CSS Layering Plugin for Vite

A Vite plugin that automatically wraps CSS modules in named CSS Cascade Layers based on glob patterns, helping you manage CSS specificity and organization.

Features

  • 🎯 Glob-based layer assignment - Automatically wrap CSS files in layers based on path patterns
  • 📝 Automatic layer order injection - Injects @layer order declaration into HTML
  • 🎨 Sass/SCSS support - Preserves @use statements at the top of files
  • Zero runtime overhead - All transformations happen at build time
  • 🛡️ TypeScript support - Full type definitions included

Why CSS Cascade Layers?

CSS Cascade Layers provide a way to organize CSS with explicit specificity control:

  • Better organization - Group related styles together
  • Specificity control - Layer order determines precedence, not specificity
  • Easier overrides - Later layers override earlier ones, regardless of selector specificity
  • Framework integration - Cleanly separate third-party, base, component, and utility styles

Learn more: MDN: CSS Cascade Layers

Installation

pnpm add vite-plugin-css-layering
# or
npm install vite-plugin-css-layering
# or
yarn add vite-plugin-css-layering

Usage

Vite

Add the plugin to your vite.config.ts:

import { defineConfig } from 'vite';
import { cssLayeringPlugin } from 'vite-plugin-css-layering';

export default defineConfig({
  plugins: [
    cssLayeringPlugin({
      layers: [
        { path: '**/reset.css', name: 'reset' },
        { path: '**/base/**/*.css', name: 'base' },
        { path: '**/components/**/*.css', name: 'components' },
        { path: '**/utilities/**/*.css', name: 'utilities' },
      ],
    }),
  ],
});

Configuration

Options

interface CSSLayeringPluginOptions {
  layers: Layer[];
  nonce?: string;
  injectOrderAs?: 'link' | 'style' | 'none';
  publicPath?: string;
}

interface Layer {
  path?: string | string[];
  exclude?: string | string[];
  name: string;
}

layers (required)

Array of layer configurations. Each layer can specify:

  • name (required) - The name of the CSS cascade layer
  • path (optional) - Glob pattern(s) to match files. Files matching this pattern will be wrapped in the layer. Uses minimatch for pattern matching.
  • exclude (optional) - Glob pattern(s) to exclude files from wrapping

Example:

layers: [
  // Wrap all CSS files in components directory
  { path: '**/components/**/*.css', name: 'components' },
  
  // Multiple patterns
  { path: ['**/ui/**/*.css', '**/widgets/**/*.css'], name: 'ui' },
  
  // Exclude specific files
  {
    path: '**/legacy/**/*.css',
    exclude: '**/legacy/vendor/**',
    name: 'legacy'
  },
  
  // Layer without path (only appears in order declaration)
  { name: 'third-party' }
]

injectOrderAs

Controls how the @layer order declaration is injected:

  • 'style' (default) - Injects as a <style> tag in HTML <head> (Vite only)
  • 'link' - Emits as a separate CSS file and injects a <link> tag
  • 'none' - No injection; you must manually add the layer order

Example output:

<!-- injectOrderAs: 'style' -->
<head>
  <style>@layer reset, base, components, utilities;</style>
</head>

<!-- injectOrderAs: 'link' -->
<head>
  <link rel="stylesheet" type="text/css" href="/static/css/layers.css">
</head>

nonce

Optional Content Security Policy nonce for the injected <style> tag (only used when injectOrderAs: 'style').

cssLayeringPlugin({
  layers: [...],
  nonce: 'random-nonce-value',
})

publicPath

Custom path for the emitted layer order CSS file (only used when injectOrderAs: 'link').

Default: /static/css/layers.css

cssLayeringPlugin({
  layers: [...],
  injectOrderAs: 'link',
  publicPath: '/assets/layers.css',
})

How It Works

CSS Transformation

When a CSS file matches a layer's path pattern:

Input (src/components/Button.css):

.button {
  color: blue;
}

Output (with { path: '**/components/**/*.css', name: 'components' }):

@layer components {
  .button {
    color: blue;
  }
}

Sass/SCSS Support

The plugin preserves @use statements at the top of files:

Input:

@use 'sass:math';
@use 'variables';

.button {
  color: $primary;
}

Output:

@use 'sass:math';
@use 'variables';

@layer components {
  .button {
    color: $primary;
  }
}

Layer Order Declaration

The plugin automatically generates and injects a layer order declaration based on your configuration:

layers: [
  { name: 'reset' },
  { name: 'base' },
  { name: 'components' },
  { name: 'utilities' },
]

Generates:

@layer reset, base, components, utilities;

Advanced Examples

Framework-specific organization

cssLayeringPlugin({
  layers: [
    { path: '**/node_modules/**/*.css', name: 'vendor' },
    { path: '**/reset.css', name: 'reset' },
    { path: '**/theme/**/*.css', name: 'theme' },
    { path: '**/components/**/*.css', name: 'components' },
    { path: '**/pages/**/*.css', name: 'pages' },
    { path: '**/utilities/**/*.css', name: 'utilities' },
  ],
})

Excluding specific patterns

cssLayeringPlugin({
  layers: [
    {
      path: '**/components/**/*.css',
      exclude: ['**/components/**/*.module.css', '**/components/legacy/**'],
      name: 'components',
    },
  ],
})

Pre-existing layers

If you have manually created layers in your CSS, you can include them in the order declaration without transforming files:

cssLayeringPlugin({
  layers: [
    { name: 'manual-layer' }, // No path - only in order declaration
    { path: '**/auto/**/*.css', name: 'auto-layer' },
  ],
})

Browser Support

CSS Cascade Layers are supported in all modern browsers:

  • Chrome 99+
  • Firefox 97+
  • Safari 15.4+
  • Edge 99+

For older browsers, consider using a CSS post-processor or polyfill.

Disclaimer

This plugin was created by porting my css-layering-webpack-plugin to Vite using AI agents powered by Warp.

License

MIT

Related Projects