# postcss-rule-unit-converter

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

Latest version **0.2.4** (published 2026-09-23) · MIT license · 0 weekly downloads

## Install

```sh
npm install postcss-rule-unit-converter
pnpm add postcss-rule-unit-converter
yarn add postcss-rule-unit-converter
bun add postcss-rule-unit-converter
```

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.2.4 |
| Published | 2026-09-23 |
| First published | 2026-04-17 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 1 |
| Unpacked size | 112 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 4 |
| Author | ice breaker <1324318532@qq.com> |
| Maintainers | icebreaker |
| Keywords | postcss, postcss-plugin, css, units, unit-conversion, rem, px, vw, rpx, responsive |

## Links

- npm: https://www.npmjs.com/package/postcss-rule-unit-converter
- Repository: https://github.com/icelib/postcss-plugins
- Issues: https://github.com/icelib/postcss-plugins/issues
- npm.io page: https://npm.io/package/postcss-rule-unit-converter

## Dependencies (1)

- [postcss-plugin-shared](https://npm.io/package/postcss-plugin-shared.md) 1.1.6

## Alternatives

- [style-dictionary](https://npm.io/package/style-dictionary.md) — 2.0M weekly downloads
- [postcss-merge-idents](https://npm.io/package/postcss-merge-idents.md) — 1.7M weekly downloads
- [@fontsource/noto-sans](https://npm.io/package/@fontsource/noto-sans.md) — 93.0K weekly downloads
- [uglifycss](https://npm.io/package/uglifycss.md) — 71.6K weekly downloads
- [mat4-interpolate](https://npm.io/package/mat4-interpolate.md) — 23.3K weekly downloads

## Recent versions

- 0.2.4 (latest) — 2026-09-23
- 0.2.3 — 2026-07-26
- 0.2.2 — 2026-04-28
- 0.2.1 — 2026-04-20
- 0.2.0 — 2026-04-18
- 0.1.0 — 2026-04-17

## README

# postcss-rule-unit-converter

Rule-driven PostCSS unit conversion plugin.

- Cookbook: [COOKBOOK.md](./COOKBOOK.md)
- API Reference: [API.md](./API.md)
- Migration Guide: [MIGRATION.md](./MIGRATION.md)
- Preset Authoring: [PRESET_AUTHORING.md](./PRESET_AUTHORING.md)

## 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

```ts
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

```ts
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`:

```ts
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

```ts
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:

```ts
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:

```ts
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:

```ts
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:

```ts
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()`

---
_Source: https://npm.io/package/postcss-rule-unit-converter · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
