CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-postcss-nested

PostCSS plugin to unwrap nested rules like how Sass does it

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

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

Install with Tessl CLI

npx tessl i tessl/npm-postcss-nested

docs

index.md

tile.json