or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-postcss-nested

PostCSS plugin to unwrap nested rules like how Sass does it

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/postcss-nested@7.0.x

To install, run

npx @tessl/cli install tessl/npm-postcss-nested@7.0.0

index.mddocs/

PostCSS Nested

PostCSS Nested is a PostCSS plugin that unwraps nested CSS rules similar to Sass syntax. It enables developers to write nested CSS with parent selector references (&), automatic selector merging, at-rule bubbling, and custom root rule handling for breaking out of nesting contexts.

Package Information

  • Package Name: postcss-nested
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install postcss-nested

Core Imports

const postcssNested = require('postcss-nested');

ESM:

import postcssNested from 'postcss-nested';

Basic Usage

const postcss = require('postcss');
const postcssNested = require('postcss-nested');

// Basic usage with default options
const result = postcss([postcssNested()])
  .process(css, { from: 'input.css' });

// With custom options
const result = postcss([
  postcssNested({
    bubble: ['phone'],
    preserveEmpty: true,
    rootRuleName: 'escape-nesting'
  })
]).process(css, { from: 'input.css' });

Input CSS:

.phone {
  &_title {
    width: 500px;
    @media (max-width: 500px) {
      width: auto;
    }
    body.is_dark & {
      color: white;
    }
  }
  img {
    display: block;
  }
}

.title {
  font-size: var(--font);
  
  @at-root html {
    --font: 16px;
  }
}

Output CSS:

.phone_title {
  width: 500px;
}
@media (max-width: 500px) {
  .phone_title {
    width: auto;
  }
}
body.is_dark .phone_title {
  color: white;
}
.phone img {
  display: block;
}

.title {
  font-size: var(--font);
}
html {
  --font: 16px;
}

Capabilities

Plugin Factory Function

Creates a PostCSS plugin instance with optional configuration.

/**
 * Creates a PostCSS plugin for processing nested CSS rules
 * @param {Options} opts - Optional configuration object
 * @returns {PostCSSPlugin} PostCSS plugin object
 */
function postcssNested(opts = {}) {
  // Returns PostCSS plugin
}

// PostCSS compatibility flag
postcssNested.postcss = true;

Configuration Options

All configuration options are optional and have sensible defaults.

interface Options {
  /**
   * Custom at-rules that should bubble to the top level.
   * Default: ['media', 'supports', 'layer', 'container', 'starting-style']
   */
  bubble?: string[];
  
  /**
   * Custom at-rules that should be unwrapped from nested contexts.
   * Default: ['document', 'font-face', 'keyframes', '-webkit-keyframes', '-moz-keyframes']
   */
  unwrap?: string[];
  
  /**
   * Whether to preserve empty selector rules after unwrapping.
   * Useful for CSS modules compatibility.
   * Default: false
   */
  preserveEmpty?: boolean;
  
  /**
   * Custom name for the at-root directive for breaking out of nesting.
   * Default: 'at-root'
   */
  rootRuleName?: string;
}

PostCSS Plugin Interface

The plugin returns a PostCSS plugin object with the required interface.

interface PostCSSPlugin {
  /** Plugin identifier for PostCSS */
  postcssPlugin: 'postcss-nested';
  
  /** Initial processing of at-root rules */
  Once(root: Root): void;
  
  /** Main rule processing function */
  Rule(rule: Rule): void;
  
  /** Final cleanup of at-root rules */
  RootExit(root: Root): void;
}

Key Features

Nested Rules

Write CSS rules inside other rules, similar to Sass:

/* Input */
.card {
  padding: 10px;
  
  .title {
    font-size: 18px;
  }
  
  .content {
    margin-top: 10px;
  }
}

/* Output */
.card {
  padding: 10px;
}

.card .title {
  font-size: 18px;
}

.card .content {
  margin-top: 10px;
}

Parent Selector References

Use & to reference the parent selector:

/* Input */
.button {
  color: blue;
  
  &:hover {
    color: red;
  }
  
  &.active {
    font-weight: bold;
  }
  
  .dark-theme & {
    color: white;
  }
}

/* Output */
.button {
  color: blue;
}

.button:hover {
  color: red;
}

.button.active {
  font-weight: bold;
}

.dark-theme .button {
  color: white;
}

At-Rule Bubbling

Media queries and other at-rules bubble to the root level:

/* Input */
.sidebar {
  width: 300px;
  
  @media (max-width: 768px) {
    width: 100%;
    
    .menu {
      display: none;
    }
  }
}

/* Output */
.sidebar {
  width: 300px;
}

@media (max-width: 768px) {
  .sidebar {
    width: 100%;
  }
  
  .sidebar .menu {
    display: none;
  }
}

At-Rule Unwrapping

Certain at-rules are unwrapped and flattened:

/* Input */
.component {
  color: black;
  
  @keyframes slide {
    from { transform: translateX(-100%); }
    to { transform: translateX(0); }
  }
}

/* Output */
.component {
  color: black;
}

@keyframes slide {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}

At-Root Directive

Break out of nested contexts using @at-root:

/* Input */
.page {
  color: black;
  
  .content {
    padding: 20px;
    
    @at-root {
      .modal {
        position: fixed;
        top: 50%;
        left: 50%;
      }
    }
  }
}

/* Output */
.page {
  color: black;
}

.page .content {
  padding: 20px;
}

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
}

At-Root with Selector

Use @at-root with a selector for targeted escaping:

/* Input */
.theme {
  .component {
    color: blue;
    
    @at-root html {
      --primary-color: blue;
    }
  }
}

/* Output */
.theme .component {
  color: blue;
}

html {
  --primary-color: blue;
}

At-Root with Filters

Control which rules escape using with and without filters:

/* Input */
@media screen {
  .component {
    color: black;
    
    @at-root (without: media) {
      .global {
        position: fixed;
      }
    }
  }
}

/* Output */
@media screen {
  .component {
    color: black;
  }
}

.global {
  position: fixed;
}

Configuration Examples

Custom Bubble Rules

Add custom at-rules to bubble to the root:

postcss([
  postcssNested({
    bubble: ['custom-query', 'my-rule']
  })
])
/* Input */
.element {
  color: black;
  
  @custom-query (min-width: 600px) {
    color: blue;
  }
}

/* Output */
.element {
  color: black;
}

@custom-query (min-width: 600px) {
  .element {
    color: blue;
  }
}

Custom Unwrap Rules

Add custom at-rules to unwrap:

postcss([
  postcssNested({
    unwrap: ['custom-keyframes']
  })
])

Preserve Empty Rules

Keep empty parent rules for CSS modules:

postcss([
  postcssNested({
    preserveEmpty: true
  })
])
/* Input */
.parent {
  .child {
    color: red;
  }
}

/* Output with preserveEmpty: true */
.parent {
}

.parent .child {
  color: red;
}

/* Output with preserveEmpty: false (default) */
.parent .child {
  color: red;
}

Custom Root Rule Name

Use a custom name for the at-root directive:

postcss([
  postcssNested({
    rootRuleName: 'escape'
  })
])
/* Input */
.nested {
  color: black;
  
  @escape {
    .global {
      position: fixed;
    }
  }
}

/* Output */
.nested {
  color: black;
}

.global {
  position: fixed;
}

Error Handling

The plugin provides helpful error messages for common issues:

  • Syntax errors in selectors: Reports missed semicolons and malformed selectors
  • Invalid at-root parameters: Clear error messages for unrecognized parameter formats
  • Selector parsing failures: Detailed error context when selectors cannot be parsed

Dependencies

  • postcss: ^8.2.14 (peer dependency)
  • postcss-selector-parser: ^7.0.0 (direct dependency)

Node.js Compatibility

  • Node.js: >=18.0