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-rule-unit-converter

v0.2.5

Published

Convert CSS units with composable PostCSS rules and built-in presets.

Readme

postcss-rule-unit-converter

Rule-driven PostCSS unit conversion plugin.

Features

  • One plugin for rpx, px, rem, vw, vh, and custom unit transforms
  • Order-based matching rules
  • Built-in common presets for two-way conversion between common units
  • Reuses the same filtering options as the other packages in this monorepo
  • Supports single presets and grouped preset collections for common workflows

Mental Model

  • Each rule matches a source unit and produces a next value.
  • Rules are evaluated in order.
  • If two rules can match the same source unit, the first matching rule wins.
  • Use composeRules(...) when you want to merge multiple preset groups and custom rules into one ordered rule list.

Usage

import postcss from 'postcss'
import unitConverter, { composeRules, presets } from 'postcss-rule-unit-converter'

const result = await postcss([
  unitConverter({
    rules: composeRules(
      presets.remToViewport({ viewportWidth: 375 }),
      presets.pxToRem({ rootValue: 16 }),
    ),
  }),
]).process('.title{font-size:1rem;margin:16px}', { from: undefined })

Grouped Presets

import postcss from 'postcss'
import unitConverter, { composeRules, presets } from 'postcss-rule-unit-converter'

const result = await postcss([
  unitConverter({
    rules: composeRules(
      presets.rpxPresetGroup({
        ratio: 2,
        rootValue: 16,
        viewportWidth: 375,
        viewportHeight: 667,
      }),
      {
        from: 'em',
        to: 'rpx',
        factor: 32,
      },
    ),
  }),
]).process('.demo{font-size:32rpx;left:10vw}', { from: undefined })

Available grouped presets:

  • presets.rpxPresetGroup() normalizes px/rem/vw/vh into rpx
  • presets.pxPresetGroup() normalizes rem/rpx/vw/vh into px
  • presets.viewportPresetGroup() normalizes px/rem/rpx into either vw or vh
  • presets.webPresetGroup() bundles common px/rem/vw/vh/rpx web-style conversions

Migrating From postcss-units-to-px

presets.unitsToPx() accepts the same unitMap shapes used by postcss-units-to-px:

import postcss from 'postcss'
import unitConverter, { presets } from 'postcss-rule-unit-converter'

const result = await postcss([
  unitConverter({
    propList: ['*'],
    rules: presets.unitsToPx({
      unitMap: {
        rem: 16,
        vw: false,
        foo: null,
      },
      transform(value, unit, context) {
        return unit === 'foo' && context.prop === 'margin' ? value * 10 : undefined
      },
    }),
  }),
]).process('.demo{font-size:1rem;width:1vw;margin:2foo}', { from: undefined })

Object unitMap values merge over the default rem/em/vw/vh/vmin/vmax/rpx map. Map and array unitMap values preserve your matcher order and do not merge defaults. Use false to skip a unit and null to fall back to transform(value, unit, context).

More Examples

import postcss from 'postcss'
import unitConverter, { presets } from 'postcss-rule-unit-converter'

const result = await postcss([
  unitConverter({
    rules: [
      presets.rpxToPx(),
      presets.pxToRpx(),
      presets.rpxToRem({ rootValue: 16 }),
      presets.rpxToVw({ viewportWidth: 375 }),
      presets.rpxToVh({ viewportHeight: 667 }),
      presets.vwToPx({ viewportWidth: 375 }),
      presets.vhToPx({ viewportHeight: 667 }),
    ],
  }),
]).process('.demo{font-size:32rpx;width:10vw;height:10vh}', { from: undefined })

Custom Presets

Single preset with exported types:

import type { PresetFactory, RemBasedPresetOptions } from 'postcss-rule-unit-converter'
import postcss from 'postcss'
import unitConverter, { definePreset } from 'postcss-rule-unit-converter'

const remToDp: PresetFactory<RemBasedPresetOptions> = definePreset((options = {}) => {
  const { rootValue = 16, minValue, to = 'dp' } = options
  return {
    from: 'rem',
    to,
    minValue,
    transform: (value, context) => {
      const resolvedRootValue = typeof rootValue === 'function'
        ? rootValue(context.input)
        : rootValue
      return value * resolvedRootValue
    },
  }
})

const result = await postcss([
  unitConverter({
    rules: [remToDp({ rootValue: 20 })],
  }),
]).process('.demo{font-size:1rem}', { from: undefined })

Grouped preset with exported types:

import type { PresetGroupFactory, RemBasedPresetOptions } from 'postcss-rule-unit-converter'
import postcss from 'postcss'
import unitConverter, { definePresetGroup } from 'postcss-rule-unit-converter'

type MyPresetOptions = RemBasedPresetOptions & {
  ratio?: number
}

const mobilePresetGroup: PresetGroupFactory<MyPresetOptions> = definePresetGroup((options = {}) => {
  const { rootValue = 16, ratio = 2 } = options
  return [
    {
      from: 'rem',
      to: 'rpx',
      transform: (value, context) => {
        const resolvedRootValue = typeof rootValue === 'function'
          ? rootValue(context.input)
          : rootValue
        return value * resolvedRootValue * ratio
      },
    },
    {
      from: 'px',
      to: 'rpx',
      factor: ratio,
    },
  ]
})

const result = await postcss([
  unitConverter({
    rules: mobilePresetGroup({ rootValue: 16, ratio: 2 }),
  }),
]).process('.demo{font-size:1rem;margin:16px}', { from: undefined })

Custom transform rule:

import postcss from 'postcss'
import unitConverter from 'postcss-rule-unit-converter'

const result = await postcss([
  unitConverter({
    rules: [
      {
        from: /^x$/,
        to: 'px',
        transform(value, context) {
          return context.prop === 'letter-spacing' ? value * 4 : value * 8
        },
      },
    ],
  }),
]).process('.demo{letter-spacing:2x;margin:2x}', { from: undefined })

Raw match context for advanced transforms:

import postcss from 'postcss'
import unitConverter from 'postcss-rule-unit-converter'

const result = await postcss([
  unitConverter({
    rules: [
      {
        from: /^(px)$/i,
        to: 'px',
        transform(value, context) {
          if (context.rawUnit === 'PX') {
            return {
              value,
              unit: 'ch',
            }
          }

          return Number(context.rawValue) / 2
        },
      },
    ],
  }),
]).process('.demo{width:40PX;height:40px}', { from: undefined })

RuleContext extends the shared replace context and also exposes:

  • fromUnit: source unit used for rule matching; it is normalized to lowercase when the plugin uses a custom or broad unit regex
  • rawUnit: matched unit text before normalization, for example PX
  • rawValue: matched numeric text before Number(...), for example 40
  • match: full matched fragment, for example 40PX

Advanced matching options:

  • unitRegex: override the generated matching regex when you need custom parsing behavior such as matching inside var(...)
  • keepZeroUnit: keep 0px, 0rem, or custom zero outputs instead of collapsing them to bare 0

String unit matchers are trimmed and normalized to lowercase. With the default generated regex, 'px' matches lowercase px; use /^px$/i or a custom unitRegex if you need uppercase unit spellings. The default regex skips quoted strings, url(...), and var(...); a custom unitRegex replaces that regex, so include those skip branches yourself if you still need them.

Rule Tips

  • Use replace: false when you want to keep fallback declarations beside converted values.
  • Use propList to keep different presets from competing on the same source unit in unrelated properties.
  • Prefer grouped presets when your project has one main unit system.
  • Prefer explicit custom rule arrays when you need tight control over rule order.
  • For presets.viewportPresetGroup(), choose viewportUnit: 'vw' or viewportUnit: 'vh' explicitly when you want one axis.
  • Prefer definePreset(...) and definePresetGroup(...) when authoring reusable presets in your own package or app config.
  • Use RuleContext.rawUnit, RuleContext.rawValue, and RuleContext.match when your transform depends on original casing or exact matched text.
  • Use unitRegex when you need non-default matching behavior such as not skipping var(...) fallbacks.
  • Use keepZeroUnit when downstream tooling expects 0 to keep its explicit unit.
  • If you want more scenario-based examples, see the cookbook.

Presets

Custom preset authoring helpers:

  • definePreset()

  • definePresetGroup()

  • type PresetFactory<TOptions>

  • type PresetGroupFactory<TOptions>

  • presets.remToPx()

  • presets.remToRpx()

  • presets.remToRpxRatio()

  • presets.remToRpxByRatio()

  • presets.remToResponsivePixel()

  • presets.remToViewport()

  • presets.remToVw()

  • presets.remToVh()

  • presets.pxToRem()

  • presets.pxToViewport()

  • presets.pxToVw()

  • presets.pxToVh()

  • presets.pxToRpx()

  • presets.rpxToPx()

  • presets.rpxToRem()

  • presets.rpxToVw()

  • presets.rpxToVh()

  • presets.vwToPx()

  • presets.vhToPx()

  • presets.vwToRem()

  • presets.vhToRem()

  • presets.vwToRpx()

  • presets.vhToRpx()

  • presets.pxPresetGroup()

  • presets.rpxPresetGroup()

  • presets.viewportPresetGroup()

  • presets.webPresetGroup()

  • presets.unitsToPx()