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

postcss-spine

v0.2.0

Published

PostCSS plugin that splits CSS into a layout-affecting "spine" and a paint-only "complement", so the spine locks in element dimensions and the rest only repaints.

Readme

PostCSS Spine

CI npm

PostCSS plugin that splits a stylesheet into two halves:

  • the spine — every declaration that affects layout (box dimensions and position); and
  • the complement — everything that is paint/composite only (colors, backgrounds, shadows, transforms, opacity, animations…).

Load the spine first and the browser lays every element out at its final size and position. Add the complement afterwards and the browser only has to repaint/composite — no element changes its dimensions, so there is no reflow. This is useful for shipping a lightweight "layout skeleton" first and deferring the visual styling, without any layout shift when it arrives.

/* input */
.card {
  width: 320px;
  padding: 16px;
  border: 1px solid #ccc;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  color: #222;
  transition: box-shadow 0.2s;
}
/* spine  — mode: 'spine' (default) */
.card {
  width: 320px;
  padding: 16px;
  border: 1px solid;
}
/* complement — mode: 'complement' */
.card {
  border-color: #ccc;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  color: #222;
  transition: box-shadow 0.2s;
}

Install

npm install --save-dev postcss-spine postcss

postcss is a peer dependency (^8.4).

Usage

The plugin works like any other PostCSS plugin. Run it twice — once per mode — to produce both stylesheets.

import postcss from 'postcss'
import spine from 'postcss-spine'

const source = fs.readFileSync('styles.css', 'utf8')

const spineCss = (await postcss([spine()]).process(source, { from: undefined })).css
const complementCss = (
  await postcss([spine({ mode: 'complement' })]).process(source, { from: undefined })
).css

CommonJS works too:

const spine = require('postcss-spine')

With a PostCSS config

Because the mode is an option, you typically want two build passes rather than a single postcss.config.js. For example, as npm scripts:

{
  "scripts": {
    "css:spine": "postcss styles.css -u postcss-spine -o dist/spine.css",
    "css:complement": "postcss styles.css -u postcss-spine -o dist/complement.css"
  }
}

(pass mode: 'complement' via your own wrapper for the second pass).

Options

| Option | Type | Default | Description | | ------------- | ------------------------- | --------- | ----------------------------------------------------------------------------------------------- | | mode | 'spine' \| 'complement' | 'spine' | 'spine' keeps only layout-affecting declarations; 'complement' keeps only the paint half. | | removeEmpty | boolean | false | Remove rules and at-rules (e.g. @media) left empty after stripping. @keyframes/@font-face are never removed. |

How declarations are classified

The plugin keeps a curated list of non-layout (paint/composite) properties; anything not on the list is assumed to affect layout. This bias is deliberate — wrongly dropping a layout rule would break dimensions, whereas keeping an extra paint rule in the spine is harmless.

  • Non-layout (dropped from the spine, kept in the complement): color, all background*, box-shadow, text-shadow, border-*-color, border-radius*, border-image*, outline*, opacity, filter, backdrop-filter, transform*, perspective*, visibility, z-index, clip, clip-path, mask*, cursor, pointer-events, user-select, and any animation* / transition* property. @keyframes at-rules are dropped from the spine.
  • Layout (kept in the spine): everything else — widths, heights, margins, padding, display, flexbox/grid, positioning, font-*, border-width, border-style, and so on.
  • Border shorthands (border, border-top/right/bottom/left) are split: the width and style tokens go to the spine, the color token becomes a border-color (or border-<side>-color) declaration in the complement.
  • Custom properties (--*) are kept in both halves, since either half may reference them.
  • Declarations inside @keyframes and @font-face are left untouched.

Vendor-prefixed properties (-webkit-, -moz-, -ms-, -o-) are classified by their unprefixed name.

Known limitations

  • Border shorthand splitting is heuristic. A token is treated as the color unless it is a recognised width (<length>, thin/medium/thick, calc()/min()/max()/clamp()) or style keyword. An ambiguous var(--x) inside a border value is therefore classified as a color.
  • Empty rules left behind after stripping declarations are kept by default; set removeEmpty: true to drop them.

Contributing

npm install
npm run lint        # ESLint (flat config, typescript-eslint)
npm run typecheck   # tsc --noEmit
npm test            # Vitest
npm run build       # tsup — dual ESM + CJS bundle with type declarations

Releases

Automated with semantic-release on every push to master, publishing to npm via trusted publishing (OIDC) — no tokens. Commit with Conventional Commits. See RELEASING.md for the setup and workflow.

License

MIT © Łukasz Sobolewski