CSS Minify Tests

Correctness tests for CSS minification tools. Each test provides a CSS input and a single canonical minified output. A tool passes if its output matches exactly.

Contribute tests on GitHub

Summary

clean-csscsskitcssnanocssoesbuildlightningcss
v5.3.3v0.0.17v7.1.2v5.0.5v0.27.3v1.31.1
Total 151 / 371 114 / 371 201 / 371 144 / 371 177 / 371 261 / 371
testclean-csscsskitcssnanocssoesbuildlightningcss
anchor 1 / 5 0 / 5 1 / 5 1 / 5 0 / 5 0 / 5
charset 1 / 2 0 / 2 1 / 2 1 / 2 0 / 2 1 / 2
colors 17 / 34 20 / 34 24 / 34 18 / 34 22 / 34 27 / 34
comments 4 / 5 2 / 5 3 / 5 3 / 5 3 / 5 2 / 5
container 3 / 6 2 / 6 1 / 6 1 / 6 1 / 6 5 / 6
counter-style 3 / 3 0 / 3 3 / 3 1 / 3 1 / 3 1 / 3
duplicates 15 / 18 5 / 18 8 / 18 14 / 18 9 / 18 12 / 18
empty-rules 4 / 4 0 / 4 4 / 4 4 / 4 4 / 4 4 / 4
escaping 3 / 12 9 / 12 3 / 12 4 / 12 10 / 12 10 / 12
font-face 3 / 6 2 / 6 3 / 6 3 / 6 4 / 6 5 / 6
gradients 0 / 5 0 / 5 0 / 5 0 / 5 1 / 5 4 / 5
import 0 / 5 0 / 5 0 / 5 0 / 5 4 / 5 0 / 5
keyframes 0 / 2 0 / 2 2 / 2 2 / 2 2 / 2 2 / 2
layer 1 / 2 0 / 2 0 / 2 0 / 2 0 / 2 2 / 2
media 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 8 / 9
merging 7 / 10 3 / 10 9 / 10 6 / 10 5 / 10 9 / 10
nesting 0 / 19 8 / 19 8 / 19 0 / 19 10 / 19 4 / 19
page 3 / 4 3 / 4 3 / 4 3 / 4 3 / 4 3 / 4
property 2 / 2 2 / 2 2 / 2 2 / 2 0 / 2 2 / 2
scope 1 / 3 0 / 3 1 / 3 1 / 3 1 / 3 1 / 3
selectors 10 / 15 3 / 15 11 / 15 5 / 15 8 / 15 13 / 15
selectors-advanced 2 / 13 3 / 13 3 / 13 3 / 13 3 / 13 3 / 13
shorthands 29 / 72 8 / 72 20 / 72 21 / 72 19 / 72 53 / 72
starting-style 0 / 2 2 / 2 1 / 2 2 / 2 2 / 2 1 / 2
supports 3 / 5 1 / 5 3 / 5 3 / 5 1 / 5 3 / 5
transforms 0 / 9 0 / 9 7 / 9 0 / 9 7 / 9 9 / 9
values 16 / 70 18 / 70 56 / 70 19 / 70 34 / 70 55 / 70
whitespace 14 / 19 15 / 19 15 / 19 17 / 19 13 / 19 15 / 19
zero-units 8 / 10 7 / 10 8 / 10 9 / 10 9 / 10 7 / 10
Subtotal 151 / 371 114 / 371 201 / 371 144 / 371 177 / 371 261 / 371

anchor

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`position-area: center center` is equivalent to `position-area: center`. When both axes are `center`, the second value can be omitted.

Source

a {
  position-area: center center;
}

Expected

a{position-area:center}

Outputs

clean-css

a{position-area:center center}

csskit

a{position-area:center center}

cssnano

a{position-area:center center}

csso

a{position-area:center center}

esbuild

a{position-area:center center}

lightningcss

a{position-area:center center}
0002
Details

`position-area: top center` shortens to `position-area: top`. When one axis is a directional keyword and the other is `center`, the `center` can be omitted as it is the default for the unspecified axis.

Source

a {
  position-area: top center;
}

Expected

a{position-area:top}

Outputs

clean-css

a{position-area:top center}

csskit

a{position-area:center top}

cssnano

a{position-area:top center}

csso

a{position-area:top center}

esbuild

a{position-area:top center}

lightningcss

a{position-area:top center}
0003
Details

A `@position-try` that only flips the block axis (bottom -> top) is equivalent to the built-in `flip-block` try tactic. The at-rule can be removed and the reference replaced with the keyword, saving the entire block.

Source

@position-try --flip {
  position-area: top;
}

a {
  position-area: bottom;
  position-try-fallbacks: --flip;
}

Expected

a{position-area:bottom;position-try-fallbacks:flip-block}

Outputs

clean-css

@position-try --flip{position-area:top}a{position-area:bottom;position-try-fallbacks:--flip}

csskit

@position-try --flip{position-area:top}a{position-area:bottom; position-try-fallbacks:--flip}

cssnano

@position-try --flip{position-area:top}a{position-area:bottom;position-try-fallbacks:--flip}

csso

@position-try --flip{position-area:top}a{position-area:bottom;position-try-fallbacks:--flip}

esbuild

@position-try --flip{position-area:top}a{position-area:bottom;position-try-fallbacks:--flip}

lightningcss

@position-try --flip{position-area: top;}a{position-area:bottom;position-try-fallbacks:--flip}
0004
Details

An empty `@position-try` block with no declarations can be removed entirely.

Source

@position-try --empty {
}

Expected

Outputs

clean-css

csskit

@position-try --empty{}

cssnano

csso

esbuild

@position-try --empty{}

lightningcss

@position-try --empty{ }
0005
Details

`position-try-fallbacks: --top, --undefined` can drop `--undefined` when no matching `@position-try --undefined` exists in the stylesheet. Unresolved dashed-ident fallbacks are skipped at runtime and have no effect.

Source

@position-try --top {
  position-area: top;
}

a {
  position-area: bottom;
  position-try-fallbacks: --top, --undefined;
}

Expected

@position-try --top{position-area:top}a{position-area:bottom;position-try-fallbacks:--top}

Outputs

clean-css

@position-try --top{position-area:top}a{position-area:bottom;position-try-fallbacks:--top,--undefined}

csskit

@position-try --top{position-area:top}a{position-area:bottom; position-try-fallbacks:--top,--undefined}

cssnano

@position-try --top{position-area:top}a{position-area:bottom;position-try-fallbacks:--top,--undefined}

csso

@position-try --top{position-area:top}a{position-area:bottom;position-try-fallbacks:--top,--undefined}

esbuild

@position-try --top{position-area:top}a{position-area:bottom;position-try-fallbacks:--top,--undefined}

lightningcss

@position-try --top{position-area: top;}a{position-area:bottom;position-try-fallbacks:--top, --undefined}
Subtotal 1 / 5 0 / 5 1 / 5 1 / 5 0 / 5 0 / 5

charset

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`@charset "UTF-8"` is redundant in modern workflows since UTF-8 is the default encoding. It should be stripped.

Source

@charset "UTF-8";
a {
  color: red;
}

Expected

a{color:red}

Outputs

clean-css

@charset "UTF-8";a{color:red}

csskit

@charset "UTF-8";a{color:red}

cssnano

a{color:red}

csso

@charset "UTF-8";a{color:red}

esbuild

@charset "UTF-8";a{color:red}

lightningcss

a{color:red}
0002
Details

Only the first @charset declaration is meaningful; subsequent @charset rules are dropped. Uses two non-UTF-8 charsets to avoid special-casing UTF-8 removal.

Source

@charset "ISO-8859-1";
@charset "ISO-8859-15";
a {
  color: red;
}

Expected

@charset "ISO-8859-1";a{color:red}

Outputs

clean-css

@charset "ISO-8859-1";a{color:red}

csskit

@charset "ISO-8859-1";@charset "ISO-8859-15";a{color:red}

cssnano

a{color:red}

csso

@charset "ISO-8859-1";a{color:red}

esbuild

@charset "UTF-8";a{color:red}

lightningcss

a{color:red}
Subtotal 1 / 2 0 / 2 1 / 2 1 / 2 0 / 2 1 / 2

colors

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`#ffffff` can be collapsed to `#fff` when all digit pairs match.

Source

a {
  color: #ffffff;
}

Expected

a{color:#fff}

Outputs

clean-css

a{color:#fff}

csskit

a{color:#fff}

cssnano

a{color:#fff}

csso

a{color:#fff}

esbuild

a{color:#fff}

lightningcss

a{color:#fff}
0002
Details

`#ff0000` is longer than the named keyword `red`. The shorter form should be used.

Source

a {
  color: #ff0000;
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

a{color:red}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0003
Details

`rgb(255, 0, 0)` is equivalent to `red` and shorter.

Source

a {
  color: rgb(255, 0, 0);
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

a{color:red}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0004
Details

Fully opaque `rgba(0, 0, 0, 1)` can drop the alpha channel and convert to a shorter hex form.

Source

a {
  color: rgba(0, 0, 0, 1);
}

Expected

a{color:#000}

Outputs

clean-css

a{color:#000}

csskit

a{color:#000}

cssnano

a{color:#000}

csso

a{color:#000}

esbuild

a{color:#000}

lightningcss

a{color:#000}
0005
Details

`rgb(210, 180, 140)` equals the named color `tan`. Tests awareness of the full CSS named-color table, not just the obvious primaries.

Source

a {
  color: rgb(210, 180, 140);
}

Expected

a{color:tan}

Outputs

clean-css

a{color:tan}

csskit

a{color:tan}

cssnano

a{color:tan}

csso

a{color:tan}

esbuild

a{color:tan}

lightningcss

a{color:tan}
0006
Details

`#aabbcc` can be collapsed to `#abc`. Same rule as `#ffffff` -> `#fff` but with a non-trivial color.

Source

a {
  color: #aabbcc;
}

Expected

a{color:#abc}

Outputs

clean-css

a{color:#abc}

csskit

a{color:#abc}

cssnano

a{color:#abc}

csso

a{color:#abc}

esbuild

a{color:#abc}

lightningcss

a{color:#abc}
0007
Details

`rgba(255, 0, 0, 0.5)` is shorter as `#ff000080` using 8-digit hex with alpha channel.

Source

a {
  color: rgba(255, 0, 0, 0.5);
}

Expected

a{color:#ff000080}

Outputs

clean-css

a{color:rgba(255,0,0,.5)}

csskit

a{color:#ff000080}

cssnano

a{color:#ff000080}

csso

a{color:rgba(255,0,0,.5)}

esbuild

a{color:#ff000080}

lightningcss

a{color:#ff000080}
0008
Details

`hsl(0, 100%, 50%)` is pure red. The named keyword `red` is shorter. Conversion applies from hsl color space, not just rgb/hex.

Source

a {
  color: hsl(0, 100%, 50%);
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

a{color:red}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0009
Details

`rgba(0, 0, 0, 0)` is fully transparent. The 4-digit hex `#0000` is the shortest equivalent representation (5 chars vs 11 for `transparent`). Note that 4-digit hex (CSS Color Level 4) may not be emitted by all minifiers -- some produce `transparent` instead, which is correct but not minimal.

Source

a {
  color: rgba(0, 0, 0, 0);
}

Expected

a{color:#0000}

Outputs

clean-css

a{color:transparent}

csskit

a{color:#0000}

cssnano

a{color:#0000}

csso

a{color:transparent}

esbuild

a{color:#0000}

lightningcss

a{color:#0000}
0010
Details

`hwb(0 0% 0%)` resolves to pure red (#ff0000) which is shorter as the named color `red`.

Source

a {
  color: hwb(0 0% 0%);
}

Expected

a{color:red}

Outputs

clean-css

a{color:hwb(0 0% 0%)}

csskit

a{color:red}

cssnano

a{color:hwb(0 0% 0%)}

csso

a{color:hwb(0 0% 0%)}

esbuild

a{color:red}

lightningcss

a{color:red}
0011
Details

`rebeccapurple` (#663399) is shorter as the 3-digit hex `#639`.

Source

a {
  color: rebeccapurple;
}

Expected

a{color:#639}

Outputs

clean-css

a{color:#663399}

csskit

a{color:#639}

cssnano

a{color:#639}

csso

a{color:#639}

esbuild

a{color:#639}

lightningcss

a{color:#639}
0012
Details

`rgb(128, 128, 0)` is #808000, which is shorter as the named color `olive`.

Source

a {
  color: rgb(128, 128, 0);
}

Expected

a{color:olive}

Outputs

clean-css

a{color:olive}

csskit

a{color:olive}

cssnano

a{color:olive}

csso

a{color:olive}

esbuild

a{color:olive}

lightningcss

a{color:olive}
0013
Details

`hsl(120, 100%, 25%)` resolves to #008000 which is shorter as the named color `green`.

Source

a {
  color: hsl(120, 100%, 25%);
}

Expected

a{color:green}

Outputs

clean-css

a{color:#007f00}

csskit

a{color:green}

cssnano

a{color:green}

csso

a{color:green}

esbuild

a{color:green}

lightningcss

a{color:green}
0014
Details

`#AABB11` should be lowercased and collapsed to `#ab1`.

Source

a {
  color: #AABB11;
}

Expected

a{color:#ab1}

Outputs

clean-css

a{color:#ab1}

csskit

a{color:#ab1}

cssnano

a{color:#ab1}

csso

a{color:#ab1}

esbuild

a{color:#ab1}

lightningcss

a{color:#ab1}
0015
Details

`rgb(143 101 98 / 43%)` can be converted to the shorter 8-digit hex `#8f65626e`. The alpha 43% maps to byte 0x6E (110). This is the standard 8-bit quantization browsers apply internally, making the conversion safe.

Source

a {
  background-color: rgb(143 101 98 / 43%);
}

Expected

a{background-color:#8f65626e}

Validate

Outputs

clean-css

a{background-color:rgb(143 101 98 / 43%)}

csskit

a{background-color:#8f65626e}

cssnano

a{background-color:#8f65626e}

csso

a{background-color:rgb(143 101 98/43%)}

esbuild

a{background-color:#8f65626e}

lightningcss

a{background-color:#8f65626e}
0016
Details

The modern space-separated `rgb(255 0 0 / 1)` syntax with full opacity should minify to the shortest equivalent named color `red`, dropping the alpha channel.

Source

a {
  color: rgb(255 0 0 / 1);
}

Expected

a{color:red}

Validate

Outputs

clean-css

a{color:rgb(255 0 0 / 1)}

csskit

a{color:red}

cssnano

a{color:red}

csso

a{color:rgb(255 0 0/1)}

esbuild

a{color:red}

lightningcss

a{color:red}
0017
Details

`hsl(120 100% 50% / 0.5)` resolves to fully saturated green at 50% opacity. This can be converted to the shorter 8-digit hex `#00ff0080`. The alpha 0.5 maps to byte 0x80 (128) under standard 8-bit quantization.

Source

a {
  color: hsl(120 100% 50% / 0.5);
}

Expected

a{color:#00ff0080}

Validate

Outputs

clean-css

a{color:hsl(120 100% 50% / .5)}

csskit

a{color:#00ff0080}

cssnano

a{color:#00ff0080}

csso

a{color:hsl(120 100% 50%/.5)}

esbuild

a{color:#00ff0080}

lightningcss

a{color:#00ff0080}
0018
Details

`color-mix(in srgb, red 50%, blue 50%)` can be resolved at build time. The 50/50 sRGB mix of red (255,0,0) and blue (0,0,255) is (128,0,128) = `purple`. Minifiers should flatten color-mix() with static arguments to the shortest form.

Source

a {
  color: color-mix(in srgb, red 50%, blue 50%);
}

Expected

a{color:purple}

Validate

Outputs

clean-css

a{color:color-mix(in srgb,red 50%,#00f 50%)}

csskit

a{color:color-mix(in srgb,red 50%,blue 50%)}

cssnano

a{color:color-mix(in srgb,red 50%,blue 50%)}

csso

a{color:color-mix(in srgb,red 50%,blue 50%)}

esbuild

a{color:color-mix(in srgb,red 50%,blue 50%)}

lightningcss

a{color:purple}
0019
Details

When `color-mix()` omits percentages (defaulting to 50%/50%) with known color arguments, minifiers can resolve the mix at build time. `color-mix(in srgb, red, blue)` is the same 50/50 mix as the explicit form, yielding (128,0,128) = `purple`.

Source

a {
  color: color-mix(in srgb, red, blue);
}

Expected

a{color:purple}

Validate

Outputs

clean-css

a{color:color-mix(in srgb,red,#00f)}

csskit

a{color:color-mix(in srgb,red,blue)}

cssnano

a{color:color-mix(in srgb,red,blue)}

csso

a{color:color-mix(in srgb,red,blue)}

esbuild

a{color:color-mix(in srgb,red,blue)}

lightningcss

a{color:purple}
0020
Details

`color(from red srgb r g b / 0.5)` passes all channels through from `red` unchanged, so it resolves to `srgb(1 0 0 / 0.5)` which is `#ff000080`. A minifier that can evaluate static relative color expressions should fold this.

Source

a {
  color: color(from red srgb r g b / 0.5);
}

Expected

a{color:#ff000080}

Outputs

clean-css

a{color:color(from red srgb r g b / .5)}

csskit

a{color:color(from red srgb r g b / .5)}

cssnano

a{color:color(from red srgb r g b/.5)}

csso

a{color:color(from red srgb r g b/.5)}

esbuild

a{color:color(from red srgb r g b / .5)}

lightningcss

a{color:color(srgb 1 0 0/.5)}
0021
Details

`none` in `rgb(none 128 0)` means "missing channel". Outside an interpolation context (like `color-mix`), missing channels resolve to `0`. So this is `rgb(0 128 0)` = `#008000` = `green`.

Source

a {
  color: rgb(none 128 0);
}

Expected

a{color:green}

Validate

Outputs

clean-css

a{color:rgb(none 128 0)}

csskit

a{color:rgb(none 128 0)}

cssnano

a{color:rgb(none 128 0)}

csso

a{color:rgb(none 128 0)}

esbuild

a{color:rgb(none 128 0)}

lightningcss

a{color:green}
0022
Details

`#ff00007a` has alpha `7a` (not `77`), so it cannot be shortened to 4-digit hex `#f007`. A minifier that naively pairs digits would produce the wrong alpha.

Source

a {
  color: #ff00007a;
}

Expected

a{color:#ff00007a}

Outputs

clean-css

a{color:#ff00007a}

csskit

a{color:#ff00007a}

cssnano

a{color:#ff00007a}

csso

a{color:#ff00007a}

esbuild

a{color:#ff00007a}

lightningcss

a{color:#ff00007a}
0023
Details

`purple` is `#800080` -- 6 chars vs 7 for hex, and the digits don't pair so it can't shorten to 3-digit hex either. A minifier must not convert to hex here.

Source

a {
  color: purple;
}

Expected

a{color:purple}

Outputs

clean-css

a{color:purple}

csskit

a{color:purple}

cssnano

a{color:purple}

csso

a{color:purple}

esbuild

a{color:purple}

lightningcss

a{color:purple}
0024
Details

In interpolation contexts like `color-mix`, a `none` (missing) channel is replaced by the corresponding channel from the other color before mixing. `rgb(none 0 0)` mixed 50/50 with `rgb(200 0 0)` yields `rgb(200 0 0)` = `#c80000` because the missing R channel adopts 200 from the other side.

Source

a {
  color: color-mix(in srgb, rgb(none 0 0) 50%, rgb(200 0 0) 50%);
}

Expected

a{color:#c80000}

Validate

Outputs

clean-css

a{color:color-mix(in srgb,rgb(none 0 0) 50%,rgb(200 0 0) 50%)}

csskit

a{color:color-mix(in srgb,rgb(none 0 0)50%,rgb(200 0 0)50%)}

cssnano

a{color:color-mix(in srgb,rgb(none 0 0) 50%,#c80000 50%)}

csso

a{color:color-mix(in srgb,rgb(none 0 0) 50%,rgb(200 0 0) 50%)}

esbuild

a{color:color-mix(in srgb,rgb(none 0 0) 50%,rgb(200 0 0) 50%)}

lightningcss

a{color:#c80000}
0025
Details

`currentColor` is a runtime keyword that inherits the computed `color` value. It cannot be replaced with a static color at build time because the inherited value depends on the DOM context.

Source

a {
  color: currentColor;
}

Expected

a{color:currentColor}

Outputs

clean-css

a{color:currentColor}

csskit

a{color:currentColor}

cssnano

a{color:currentColor}

csso

a{color:currentColor}

esbuild

a{color:currentColor}

lightningcss

a{color:currentColor}
0026
Details

`oklch()` values can have their numbers minified (leading zero removal) but must not be converted to sRGB hex since the oklch gamut exceeds sRGB.

Source

a {
  color: oklch(0.7 0.15 180);
}

Expected

a{color:oklch(.7 .15 180)}

Outputs

clean-css

a{color:oklch(.7 .15 180)}

csskit

a{color:#00bca2}

cssnano

a{color:oklch(.7 .15 180)}

csso

a{color:oklch(.7 .15 180)}

esbuild

a{color:oklch(.7 .15 180)}

lightningcss

a{color:oklch(70% .15 180)}
0027
Details

`oklab()` values can have their numbers minified (leading zero removal) but must not be converted to hex since oklab colors may fall outside the sRGB gamut.

Source

a {
  color: oklab(0.5 -0.1 0.1);
}

Expected

a{color:oklab(.5 -.1 .1)}

Outputs

clean-css

a{color:oklab(.5 -.1 .1)}

csskit

a{color:#3c740a}

cssnano

a{color:oklab(.5 -.1 .1)}

csso

a{color:oklab(.5-.1 .1)}

esbuild

a{color:#3c740a}

lightningcss

a{color:oklab(50% -.1 .1)}
0028
Details

`color-mix(in srgb, red, red)` mixes a color with itself at equal proportions. The result is always the original color regardless of the interpolation space.

Source

a {
  color: color-mix(in srgb, red, red);
}

Expected

a{color:red}

Outputs

clean-css

a{color:color-mix(in srgb,red,red)}

csskit

a{color:color-mix(in srgb,red,red)}

cssnano

a{color:color-mix(in srgb,red,red)}

csso

a{color:color-mix(in srgb,red,red)}

esbuild

a{color:color-mix(in srgb,red,red)}

lightningcss

a{color:red}
0029
Details

`color-mix(in oklch, red 50%, blue 50%)` can be resolved at build time to `oklch(53.9985% .285449 326.643)`. A minifier that resolves the mix must do so in oklch space -- mixing in srgb produces a different color. Keeping the result in oklch avoids lossy gamut mapping to sRGB hex.

Source

a {
  color: color-mix(in oklch, red 50%, blue 50%);
}

Expected

a{color:oklch(53.9985% .285449 326.643)}

Validate

Outputs

clean-css

a{color:color-mix(in oklch,red 50%,#00f 50%)}

csskit

a{color:color-mix(in oklch,red 50%,blue 50%)}

cssnano

a{color:color-mix(in oklch,red 50%,blue 50%)}

csso

a{color:color-mix(in oklch,red 50%,blue 50%)}

esbuild

a{color:color-mix(in oklch,red 50%,blue 50%)}

lightningcss

a{color:oklch(53.9985% .285449 326.643)}
0030 ERR
Details

`color(display-p3 1 0 0)` is a P3 red that cannot be represented in sRGB. Converting to hex or rgb would clamp the value and lose the wider gamut intent. The color function and space must be preserved.

Source

a {
  color: color(display-p3 1 0 0);
}

Expected

a{color:color(display-p3 1 0 0)}

Outputs

clean-css

a{color:color(display-p3 1 0 0)}

csskit

Command failed: "/home/runner/work/css-minify-tests/css-minify-tests/node_modules/.bin/csskit" min -

thread 'main' (2777) panicked at crates/css_ast/src/functions/color_function.rs:116:9:
not yet implemented
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Aborted (core dumped)

cssnano

a{color:color(display-p3 1 0 0)}

csso

a{color:color(display-p3 1 0 0)}

esbuild

a{color:color(display-p3 1 0 0)}

lightningcss

a{color:color(display-p3 1 0 0)}
0031
Details

`rgba(255, 255, 255, 1.0)` inside a `color-mix()` with a `var()` second argument can still be shortened to `#fff`. The var() prevents resolving the mix, but the literal color is independently minifiable.

Source

a {
  color: color-mix(in oklch, rgba(255, 255, 255, 1.0), var(--foo));
}

Expected

a{color:color-mix(in oklch,#fff,var(--foo))}

Validate

Outputs

clean-css

a{color:color-mix(in oklch,#fff,var(--foo))}

csskit

a{color:color-mix(in oklch,rgba(255,255,255,1),var(--foo))}

cssnano

a{color:color-mix(in oklch,#fff,var(--foo))}

csso

a{color:color-mix(in oklch,#fff,var(--foo))}

esbuild

a{color:color-mix(in oklch,rgba(255,255,255,1),var(--foo))}

lightningcss

a{color:color-mix(in oklch, #fff, var(--foo))}
0032
Details

`hsl(0, 100%, 50%)` inside a `color-mix()` with a `var()` second argument can still be shortened to `red`. The var() prevents resolving the mix, but each literal color argument is independently minifiable.

Source

a {
  color: color-mix(in srgb, hsl(0, 100%, 50%), var(--foo));
}

Expected

a{color:color-mix(in srgb,red,var(--foo))}

Validate

Outputs

clean-css

a{color:color-mix(in srgb,red,var(--foo))}

csskit

a{color:color-mix(in srgb,hsl(0,100%,50%),var(--foo))}

cssnano

a{color:color-mix(in srgb,red,var(--foo))}

csso

a{color:color-mix(in srgb,red,var(--foo))}

esbuild

a{color:color-mix(in srgb,hsl(0,100%,50%),var(--foo))}

lightningcss

a{color:color-mix(in srgb, red, var(--foo))}
0033
Details

Per CSS Color 5, `oklab` is the default interpolation method for `color-mix()` when none is specified. `in oklab` can be elided, saving 9 bytes. Browsers already support the omitted form.

Source

a {
  color: color-mix(in oklab, var(--a), var(--b));
}

Expected

a{color:color-mix(var(--a),var(--b))}

Validate

Outputs

clean-css

a{color:color-mix(in oklab,var(--a),var(--b))}

csskit

a{color:color-mix(in oklab,var(--a),var(--b))}

cssnano

a{color:color-mix(in oklab,var(--a),var(--b))}

csso

a{color:color-mix(in oklab,var(--a),var(--b))}

esbuild

a{color:color-mix(in oklab,var(--a),var(--b))}

lightningcss

a{color:color-mix(in oklab, var(--a), var(--b))}
0034
Details

`blue` (4 chars) converts to `#00f` (4 chars) which looks like a no-op, but `#` is an unambiguous token start so the preceding space can be dropped: `1px solid#00f` saves 1 byte over `1px solid blue`.

Source

a {
  border: 1px solid blue;
}

Expected

a{border:1px solid#00f}

Outputs

clean-css

a{border:1px solid #00f}

csskit

a{border:1px solid blue}

cssnano

a{border:1px solid blue}

csso

a{border:1px solid #00f}

esbuild

a{border:1px solid blue}

lightningcss

a{border:1px solid #00f}
Subtotal 17 / 34 20 / 34 24 / 34 18 / 34 22 / 34 27 / 34

comments

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Standard `/* ... */` comments should be stripped entirely.

Source

/* comment */
a {
  color: red;
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

a{color:red}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0002
Details

A comment embedded within a declaration value (`color: /* inline */ red`) must be removed without breaking the surrounding value.

Source

a {
  color: /* inline */ red;
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

a{color:red}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0003
Details

Comments starting with `/*!` are license/legal markers and must be preserved. Stripping them can cause legal compliance issues.

Source

/*! important */
a {
  color: red;
}

Expected

/*! important */a{color:red}

Outputs

clean-css

/*! important */a{color:red}

csskit

a{color:red}

cssnano

/*! important */a{color:red}

csso

/*! important */
a{color:red}

esbuild

/*! important */a{color:red}

lightningcss

/*! important */
a{color:red}
0004
Details

`--bar: a/**/b` tokenizes as `ident:a` `ident:b` with no whitespace between them. Replacing the comment with a space produces `a b` which is a different token sequence. This matters for `@container style()` queries matching against custom property values.

Source

a {
  --bar: a/**/b;
}

Expected

a{--bar:a/**/b}

Outputs

clean-css

a{--bar:a/**/b}

csskit

a{--bar:a b}

cssnano

a{--bar:a b}

csso

a{--bar:a/**/b}

esbuild

a{--bar: ab}

lightningcss

a{--bar:ab}
0005
Details

`@container style(--bar: a/**/b)` uses a comment as a zero-width token separator. Replacing it with a space or removing it entirely changes the token sequence the query matches against, breaking the correspondence with the declaration `--bar: a/**/b`.

Source

@container style(--bar: a/**/b) {
  a {
    color: red;
  }
}

Expected

@container style(--bar:a/**/b){a{color:red}}

Outputs

clean-css

@container style(--bar:ab){a{color:red}}

csskit

@container style(--bar:a b){a{color:red}}

cssnano

@container style(--bar: a b){a{color:red}}

csso

@container style(--bar: a/**/b){a{color:red}}

esbuild

@container style(--bar: ab){a{color:red}}

lightningcss

@container style(--bar:ab){a{color:red}}
Subtotal 4 / 5 2 / 5 3 / 5 3 / 5 3 / 5 2 / 5

container

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Remove whitespace inside `@container` query while preserving the container name and condition. The space between name and `(` is required.

Source

@container sidebar (min-width: 700px) {
  a {
    color: red;
  }
}

Expected

@container sidebar (min-width:700px){a{color:red}}

Outputs

clean-css

@container sidebar (min-width:700px){a{color:red}}

csskit

@container sidebar (min-width:700px){a{color:red}}

cssnano

@container sidebar (min-width: 700px){a{color:red}}

csso

@container sidebar (min-width: 700px){a{color:red}}

esbuild

@container sidebar (min-width: 700px){a{color:red}}

lightningcss

@container sidebar (width>=700px){a{color:red}}
0002
Details

An `@container` rule with no child rules has no effect and can be removed.

Source

@container (min-width: 700px) {
}

Expected

Outputs

clean-css

csskit

@container (min-width:700px){}

cssnano

csso

esbuild

lightningcss

0003
Details

`style()` container queries must be preserved. Whitespace around `:` inside the condition can be removed but the `style()` function itself must not be stripped as unknown.

Source

@container style(--theme: dark) {
  a {
    color: white;
  }
}

Expected

@container style(--theme:dark){a{color:#fff}}

Outputs

clean-css

@container style(--theme:dark){a{color:#fff}}

csskit

@container style(--theme:dark){a{color:#fff}}

cssnano

@container style(--theme: dark){a{color:#fff}}

csso

@container style(--theme: dark){a{color:#fff}}

esbuild

@container style(--theme: dark){a{color:#fff}}

lightningcss

@container style(--theme:dark){a{color:#fff}}
0004
Details

`@container (min-width: 700px)` can be shortened to `@container (width>=700px)` using range syntax. Container queries support the same range forms as media queries.

Source

@container (min-width: 700px) {
  a {
    color: red;
  }
}

Expected

@container (width>=700px){a{color:red}}

Outputs

clean-css

@container (min-width:700px){a{color:red}}

csskit

@container (min-width:700px){a{color:red}}

cssnano

@container (min-width: 700px){a{color:red}}

csso

@container (min-width: 700px){a{color:red}}

esbuild

@container (min-width: 700px){a{color:red}}

lightningcss

@container (width>=700px){a{color:red}}
0005
Details

`@container (max-width: 500px)` can be shortened to `@container (width<=500px)` using range syntax.

Source

@container (max-width: 500px) {
  a {
    color: red;
  }
}

Expected

@container (width<=500px){a{color:red}}

Outputs

clean-css

@container (max-width:500px){a{color:red}}

csskit

@container (max-width:500px){a{color:red}}

cssnano

@container (max-width: 500px){a{color:red}}

csso

@container (max-width: 500px){a{color:red}}

esbuild

@container (max-width: 500px){a{color:red}}

lightningcss

@container (width<=500px){a{color:red}}
0006
Details

`@container sidebar (min-width: 700px)` can use range syntax for the condition while preserving the container name: `@container sidebar (width>=700px)`.

Source

@container sidebar (min-width: 700px) {
  a {
    color: red;
  }
}

Expected

@container sidebar (width>=700px){a{color:red}}

Outputs

clean-css

@container sidebar (min-width:700px){a{color:red}}

csskit

@container sidebar (min-width:700px){a{color:red}}

cssnano

@container sidebar (min-width: 700px){a{color:red}}

csso

@container sidebar (min-width: 700px){a{color:red}}

esbuild

@container sidebar (min-width: 700px){a{color:red}}

lightningcss

@container sidebar (width>=700px){a{color:red}}
Subtotal 3 / 6 2 / 6 1 / 6 1 / 6 1 / 6 5 / 6

counter-style

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Basic whitespace minification inside a `@counter-style` rule. The space between the at-keyword and the name must be preserved (unlike `@page`), but all other internal whitespace is removable.

Source

@counter-style thumbs {
  system: cyclic;
  symbols: "\1F44D";
  suffix: " ";
}

Expected

@counter-style thumbs{system:cyclic;symbols:"\1F44D";suffix:" "}

Outputs

clean-css

@counter-style thumbs{system:cyclic;symbols:"\1F44D";suffix:" "}

csskit

@counter-style thumbs{system:cyclic;symbols:"👍";suffix:" "}

cssnano

@counter-style thumbs{system:cyclic;symbols:"\1F44D";suffix:" "}

csso

@counter-style thumbs{system:cyclic;symbols:"👍";suffix:" "}

esbuild

@counter-style thumbs{system:cyclic;symbols:"\1f44d";suffix:" "}

lightningcss

@counter-style thumbs{system:cyclic;symbols:"👍";suffix:" "}
0002
Details

An empty `@counter-style` with no descriptors can be removed entirely.

Source

@counter-style empty {
}

Expected

Outputs

clean-css

csskit

@counter-style empty{}

cssnano

csso

esbuild

@counter-style empty{}

lightningcss

@counter-style empty{}
0003
Details

`symbols: "(" ")"` must keep quotes. Parentheses are not valid unquoted ident tokens and stripping quotes would produce a parse error.

Source

@counter-style paren {
  system: fixed;
  symbols: "(" ")";
}

Expected

@counter-style paren{system:fixed;symbols:"(" ")"}

Outputs

clean-css

@counter-style paren{system:fixed;symbols:"(" ")"}

csskit

@counter-style paren{system:fixed;symbols:"("")"}

cssnano

@counter-style paren{system:fixed;symbols:"(" ")"}

csso

@counter-style paren{system:fixed;symbols:"("")"}

esbuild

@counter-style paren{system:fixed;symbols:"(" ")"}

lightningcss

@counter-style paren{system:fixed;symbols:"(" ")"}
Subtotal 3 / 3 0 / 3 3 / 3 1 / 3 1 / 3 1 / 3

duplicates

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

When a property is declared twice in the same rule, the last value wins. The earlier duplicate should be removed.

Source

a {
  color: red;
  color: blue;
}

Expected

a{color:#00f}

Outputs

clean-css

a{color:#00f}

csskit

a{color:red;color:blue}

cssnano

a{color:red;color:blue}

csso

a{color:#00f}

esbuild

a{color:red;color:#00f}

lightningcss

a{color:#00f}
0002
Details

When two adjacent rules share the same selector and the later one completely overrides the earlier, the earlier rule can be removed.

Source

a {
  color: red;
}
a {
  color: blue;
}

Expected

a{color:#00f}

Outputs

clean-css

a{color:#00f}

csskit

a{color:red}a{color:blue}

cssnano

a{color:red;color:blue}

csso

a{color:#00f}

esbuild

a{color:red}a{color:#00f}

lightningcss

a{color:#00f}
0003
Details

When a later rule completely overrides an earlier rule with the same selector, the earlier rule should be removed even with other rules between them.

Source

a {
  color: red;
  font-size: 16px;
}
b {
  margin: 0;
}
a {
  color: blue;
  font-size: 16px;
}

Expected

b{margin:0}a{color:#00f;font-size:16px}

Outputs

clean-css

b{margin:0}a{color:#00f;font-size:16px}

csskit

a{color:red;font-size:16px}b{margin:0}a{color:blue;font-size:16px}

cssnano

a{color:red}b{margin:0}a{color:blue;font-size:16px}

csso

a{color:#00f;font-size:16px}b{margin:0}

esbuild

a{color:red;font-size:16px}b{margin:0}a{color:#00f;font-size:16px}

lightningcss

b{margin:0}a{color:#00f;font-size:16px}
0004
Details

Duplicate selectors in a selector list should be deduplicated.

Source

h1, h2, h3, h4, h5, h5, h6 {
  color: red;
}

Expected

h1,h2,h3,h4,h5,h6{color:red}

Outputs

clean-css

h1,h2,h3,h4,h5,h6{color:red}

csskit

h1,h2,h3,h4,h5,h5,h6{color:red}

cssnano

h1,h2,h3,h4,h5,h6{color:red}

csso

h1,h2,h3,h4,h5,h6{color:red}

esbuild

h1,h2,h3,h4,h5,h6{color:red}

lightningcss

h1,h2,h3,h4,h5,h5,h6{color:red}
0005
Details

The plain `1rem` fallback and `env()` declaration are progressive enhancement. Removing the "duplicate" breaks browsers that don't support `env()`.

Source

a {
  padding-top: 1rem;
  padding-top: calc(1rem + env(safe-area-inset-top));
}

Expected

a{padding-top:1rem;padding-top:calc(1rem + env(safe-area-inset-top))}

Outputs

clean-css

a{padding-top:1rem;padding-top:calc(1rem + env(safe-area-inset-top))}

csskit

a{padding-top:1rem;padding-top:calc(1rem + env(safe-area-inset-top))}

cssnano

a{padding-top:calc(1rem + env(safe-area-inset-top))}

csso

a{padding-top:1rem;padding-top:calc(1rem + env(safe-area-inset-top))}

esbuild

a{padding-top:1rem;padding-top:calc(1rem + env(safe-area-inset-top))}

lightningcss

a{padding-top:1rem;padding-top:calc(1rem + env(safe-area-inset-top))}
0006
Details

When a property is declared with `!important` and then redeclared without it, the non-important declaration loses and should be removed. The `!important` declaration always wins regardless of order.

Source

a {
  color: red !important;
  color: blue;
}

Expected

a{color:red!important}

Validate

Outputs

clean-css

a{color:red!important}

csskit

a{color:red!important;color:blue}

cssnano

a{color:red!important;color:blue}

csso

a{color:red!important}

esbuild

a{color:red!important;color:#00f}

lightningcss

a{color:#00f;color:red!important}
0007
Details

When two rules have the same selector and identical declarations, the duplicate rule is fully redundant and should be removed.

Source

a {
  color: red;
  font-size: 16px;
}
a {
  color: red;
  font-size: 16px;
}

Expected

a{color:red;font-size:16px}

Outputs

clean-css

a{color:red;font-size:16px}

csskit

a{color:red;font-size:16px}a{color:red;font-size:16px}

cssnano

a{color:red;font-size:16px}

csso

a{color:red;font-size:16px}

esbuild

a{color:red;font-size:16px}

lightningcss

a{color:red;font-size:16px}
0008
Details

With a modern browserslist target, `-webkit-transform` is unnecessary because all target browsers support unprefixed `transform`. The prefix is dead code.

Source

a {
  -webkit-transform: rotate(45deg);
  transform: rotate(45deg);
}

Expected

a{transform:rotate(45deg)}

Outputs

clean-css

a{-webkit-transform:rotate(45deg);transform:rotate(45deg)}

csskit

a{-webkit-transform:rotate(45deg);transform:rotate(45deg)}

cssnano

a{-webkit-transform:rotate(45deg);transform:rotate(45deg)}

csso

a{-webkit-transform:rotate(45deg);transform:rotate(45deg)}

esbuild

a{-webkit-transform:rotate(45deg);transform:rotate(45deg)}

lightningcss

a{transform:rotate(45deg)}
0009
Details

Duplicate compound/complex selectors within a selector list should be deduplicated, including selectors beyond simple type selectors.

Source

.nav .item, .footer, .nav .item {
  color: red;
}

Expected

.nav .item,.footer{color:red}

Outputs

clean-css

.footer,.nav .item{color:red}

csskit

.nav.item,.footer,.nav.item{color:red}

cssnano

.footer,.nav .item{color:red}

csso

.footer,.nav .item{color:red}

esbuild

.nav .item,.footer{color:red}

lightningcss

.nav .item,.footer,.nav .item{color:red}
0010
Details

Two rules with the same selectors in different order and identical declarations are equivalent. Remove the duplicate by normalizing selector order.

Source

h1, h2 {
  color: red;
}
h2, h1 {
  color: red;
}

Expected

h1,h2{color:red}

Outputs

clean-css

h1,h2{color:red}

csskit

h1,h2{color:red}h2,h1{color:red}

cssnano

h1,h2{color:red}

csso

h1,h2{color:red}

esbuild

h1,h2{color:red}

lightningcss

h1,h2,h2,h1{color:red}
0011
Details

`.foo .bar{...}` and `.foo { .bar{...} }` resolve to the same selector. When the declarations are identical, the duplicate should be removed.

Source

.foo .bar {
  color: red;
}
.foo {
  .bar {
    color: red;
  }
}

Expected

.foo .bar{color:red}

Outputs

clean-css

.foo .bar{color:red}

csskit

.foo.bar{color:red}.foo{.bar{color:red}}

cssnano

.foo .bar{color:red}.foo{.bar{color:red}}

csso

.foo .bar{color:red}

esbuild

.foo .bar{color:red}.foo{.bar{color:red}}

lightningcss

.foo .bar{color:red}.foo{& .bar{color:red}}
0012
Details

When a later rule for the same selector contains all declarations of an earlier rule plus additional ones, the earlier rule is redundant and can be removed.

Source

a {
  color: red;
}
a {
  color: red;
  font-size: 1rem;
}

Expected

a{color:red;font-size:1rem}

Outputs

clean-css

a{color:red;font-size:1rem}

csskit

a{color:red}a{color:red;font-size:1rem}

cssnano

a{color:red;font-size:1rem}

csso

a{color:red;font-size:1rem}

esbuild

a{color:red}a{color:red;font-size:1rem}

lightningcss

a{color:red;font-size:1rem}
0013
Details

When a custom property has both a normal and `!important` declaration, the `!important` one wins regardless of order. The normal declaration is dead code. Note the space before `!important` is part of the syntax, not the value.

Source

a {
  --color: blue;
  --color: red !important;
}

Expected

a{--color:red !important}

Outputs

clean-css

a{--color:blue;--color:red!important}

csskit

a{--color:blue;--color:red !important}

cssnano

a{--color:blue;--color:red!important}

csso

a{--color:blue;--color:red!important}

esbuild

a{--color: blue;--color: red !important}

lightningcss

a{--color:blue;--color:red!important}
0014
Details

`border-top: 1px solid blue` followed by `border-top: 2px solid red` is a duplicate declaration. The last one wins and the first can be removed.

Source

a {
  border-top: 1px solid blue;
  border-top: 2px solid red;
}

Expected

a{border-top:2px solid red}

Outputs

clean-css

a{border-top:2px solid red}

csskit

a{border-top:1px solid blue;border-top:2px solid red}

cssnano

a{border-top:2px solid red}

csso

a{border-top:2px solid red}

esbuild

a{border-top:1px solid blue;border-top:2px solid red}

lightningcss

a{border-top:2px solid red}
0015
Details

`border-top` is physical and `border-block` is logical. In `horizontal-tb` writing mode `border-block` maps to top/bottom, but in vertical writing modes it maps to left/right. Removing `border-top` would change rendering in some writing modes.

Source

a {
  border-top: 1px solid red;
  border-block: 2px solid red;
}

Expected

a{border-top:1px solid red;border-block:2px solid red}

Outputs

clean-css

a{border-top:1px solid red;border-block:2px solid red}

csskit

a{border-top:1px solid red;border-block:2px solid red}

cssnano

a{border-block:2px solid red;border-top:1px solid red}

csso

a{border-top:1px solid red;border-block:2px solid red}

esbuild

a{border-top:1px solid red;border-block:2px solid red}

lightningcss

a{border-top:1px solid red;border-block:2px solid red}
0016
Details

`margin-left` is physical and `margin-inline-start` is logical. In LTR `margin-inline-start` maps to `margin-left`, but in RTL it maps to `margin-right`. Both declarations interact via cascade order and writing mode, so removing either changes rendering in some configurations.

Source

a {
  margin-left: 10px;
  margin-inline-start: 20px;
}

Expected

a{margin-left:10px;margin-inline-start:20px}

Outputs

clean-css

a{margin-left:10px;margin-inline-start:20px}

csskit

a{margin-left:10px;margin-inline-start:20px}

cssnano

a{margin-left:10px;margin-inline-start:20px}

csso

a{margin-left:10px;margin-inline-start:20px}

esbuild

a{margin-left:10px;margin-inline-start:20px}

lightningcss

a{margin-left:10px;margin-inline-start:20px}
0017
Details

`padding-top` is physical and `padding-block-start` is logical. In `horizontal-tb` they resolve to the same edge, but in vertical writing modes `padding-block-start` maps to `padding-left` or `padding-right`. Removing either declaration changes rendering in some writing modes.

Source

a {
  padding-top: 10px;
  padding-block-start: 20px;
}

Expected

a{padding-top:10px;padding-block-start:20px}

Outputs

clean-css

a{padding-top:10px;padding-block-start:20px}

csskit

a{padding-top:10px;padding-block-start:20px}

cssnano

a{padding-top:10px;padding-block-start:20px}

csso

a{padding-top:10px;padding-block-start:20px}

esbuild

a{padding-top:10px;padding-block-start:20px}

lightningcss

a{padding-top:10px;padding-block-start:20px}
0018
Details

`inset-block-start` is logical and `top` is physical. In `horizontal-tb` they resolve to the same edge, but in vertical writing modes `inset-block-start` maps to `left` or `right`. Removing either changes rendering in some writing modes.

Source

a {
  inset-block-start: 10px;
  top: 20px;
}

Expected

a{inset-block-start:10px;top:20px}

Outputs

clean-css

a{inset-block-start:10px;top:20px}

csskit

a{inset-block-start:10px;top:20px}

cssnano

a{inset-block-start:10px;top:20px}

csso

a{inset-block-start:10px;top:20px}

esbuild

a{inset-block-start:10px;top:20px}

lightningcss

a{inset-block-start:10px;top:20px}
Subtotal 15 / 18 5 / 18 8 / 18 14 / 18 9 / 18 12 / 18

empty-rules

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`a {}` contains no declarations and should be removed entirely. Empty rules waste bytes and have no effect on rendering.

Source

a {}
b {
  color: red;
}

Expected

b{color:red}

Outputs

clean-css

b{color:red}

csskit

a{}b{color:red}

cssnano

b{color:red}

csso

b{color:red}

esbuild

b{color:red}

lightningcss

b{color:red}
0002
Details

A rule containing only a comment has no declarations and should be removed. Comments are stripped during minification so the rule body becomes empty.

Source

a {
  /* this rule is empty */
}
b {
  color: red;
}

Expected

b{color:red}

Outputs

clean-css

b{color:red}

csskit

a{}b{color:red}

cssnano

b{color:red}

csso

b{color:red}

esbuild

b{color:red}

lightningcss

b{color:red}
0003
Details

An `@media` block with no rules inside should be removed entirely. It has no effect on rendering.

Source

@media screen {
}
b {
  color: red;
}

Expected

b{color:red}

Outputs

clean-css

b{color:red}

csskit

@media screen{}b{color:red}

cssnano

b{color:red}

csso

b{color:red}

esbuild

b{color:red}

lightningcss

b{color:red}
0004
Details

An empty nested rule (`& b {}`) inside a parent should be removed while preserving the parent's own declarations.

Source

a {
  color: red;
  & b {
  }
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

a{color:red;&b{}}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
Subtotal 4 / 4 0 / 4 4 / 4 4 / 4 4 / 4 4 / 4

escaping

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`\66 oo` is a CSS escape sequence for `foo`. Minifiers should resolve escape sequences in identifiers to their plain UTF-8 equivalents when safe.

Source

\66 oo {
  color: red;
}

Expected

foo{color:red}

Validate

Outputs

clean-css

\66 oo{color:red}

csskit

foo{color:red}

cssnano

\66 oo{color:red}

csso

\66 oo{color:red}

esbuild

foo{color:red}

lightningcss

foo{color:red}
0002
Details

`'\66 oo'` should resolve the escape to `"foo"`. Tests both escape resolution within string values and normalization from single to double quotes.

Source

a {
  content: '\66 oo';
}

Expected

a{content:"foo"}

Validate

Outputs

clean-css

a{content:'\66 oo'}

csskit

a{content:"foo"}

cssnano

a{content:"\66 oo"}

csso

a{content:"foo"}

esbuild

a{content:"foo"}

lightningcss

a{content:"foo"}
0003
Details

`.\66\6f\6f` encodes each letter of `foo` as a separate hex escape. All three can be resolved to plain ASCII, producing `.foo`.

Source

.\66\6f\6f {
  color: red;
}

Expected

.foo{color:red}

Validate

Outputs

clean-css

.\66\6f\6f{color:red}

csskit

.foo{color:red}

cssnano

.\66\6f\6f {color:red}

csso

.\66\6f\6f {color:red}

esbuild

.foo{color:red}

lightningcss

.foo{color:red}
0004
Details

`\2d foo` encodes a hyphen-minus followed by `foo`, producing `-foo`. The hyphen is valid mid-identifier so the escape can be resolved to a literal `-`.

Source

\2d foo {
  color: red;
}

Expected

-foo{color:red}

Validate

Outputs

clean-css

\2d foo{color:red}

csskit

-foo{color:red}

cssnano

\2d foo{color:red}

csso

\2d foo{color:red}

esbuild

-foo{color:red}

lightningcss

-foo{color:red}
0005
Details

`hr\65 f` uses a hex escape for `e` inside the attribute name, producing `href`. The escape can be resolved to a plain ASCII character.

Source

a[hr\65 f="x"] {
  color: red;
}

Expected

a[href="x"]{color:red}

Validate

Outputs

clean-css

a[hr\65f="x"]{color:red}

csskit

a[href="x"]{color:red}

cssnano

a[href=x]{color:red}

csso

a[hr\65 f=x]{color:red}

esbuild

a[href=x]{color:red}

lightningcss

a[href=x]{color:red}
0006
Details

`.\31 23` is the class selector `.123`. The leading digit must remain escaped because bare `123` is not a valid CSS identifier start. A minifier that resolves this to `.123` would produce an invalid selector.

Source

.\31 23 {
  color: red;
}

Expected

.\31 23{color:red}

Validate

Outputs

clean-css

.\31 23{color:red}

csskit

.123{color:red}

cssnano

.\31 23{color:red}

csso

.\31 23{color:red}

esbuild

.\31 23{color:red}

lightningcss

.\31 23{color:red}
0007
Details

`"\a"` is a CSS escape for a newline character (U+000A). It cannot be replaced with a literal newline inside the string, as that would terminate the string.

Source

a {
  content: "\a";
}

Expected

a{content:"\a"}

Outputs

clean-css

a{content:"\a"}

csskit

a{content:"
"}

cssnano

a{content:"\a"}

csso

a{content:"\a"}

esbuild

a{content:"\a"}

lightningcss

a{content:"\a "}
0008
Details

`.\@` escapes the `@` sign to use it in a class name. The escape must be preserved because `@` unescaped is not valid in an identifier.

Source

.\@ {
  color: red;
}

Expected

.\@{color:red}

Outputs

clean-css

.\@{color:red}

csskit

.@{color:red}

cssnano

.\@{color:red}

csso

.\@{color:red}

esbuild

.\@{color:red}

lightningcss

.\@{color:red}
0009
Details

`#\66 oo` is the ID selector `#foo` with a hex escape for `f`. The escape can be resolved since `foo` is a valid identifier.

Source

#\66 oo {
  color: red;
}

Expected

#foo{color:red}

Validate

Outputs

clean-css

#\66 oo{color:red}

csskit

#foo{color:red}

cssnano

#\66 oo{color:red}

csso

#\66 oo{color:red}

esbuild

#foo{color:red}

lightningcss

#foo{color:red}
0010
Details

`a.\62 ar` is `a.bar` with a hex escape for `b`. Escape resolution applies in compound selectors (element + class).

Source

a.\62 ar {
  color: red;
}

Expected

a.bar{color:red}

Validate

Outputs

clean-css

a.\62 ar{color:red}

csskit

a.bar{color:red}

cssnano

a.\62 ar{color:red}

csso

a.\62 ar{color:red}

esbuild

a.bar{color:red}

lightningcss

a.bar{color:red}
0011
Details

`\63 olor` encodes the `c` in `color` as a hex escape. Minifiers should resolve escape sequences in property names to their plain equivalents.

Source

a {
  \63 olor: red;
}

Expected

a{color:red}

Validate

Outputs

clean-css

csskit

a{color:red}

cssnano

a{\63 olor:red}

csso

a{\63 olor:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0012
Details

`--\66 oo` encodes `f` as a hex escape in a custom property name, resolving to `--foo`. The same escape appears in `var(--\66 oo)` and must resolve consistently.

Source

a {
  --\66 oo: red;
  color: var(--\66 oo);
}

Expected

a{--foo:red;color:var(--foo)}

Validate

Outputs

clean-css

a{color:var(--\66 oo)}

csskit

a{--foo:red;color:var(--foo)}

cssnano

a{--\66 oo:red;color:var(--\66 oo)}

csso

a{--\66 oo:red;color:var(--\66 oo)}

esbuild

a{--foo: red;color:var(--foo)}

lightningcss

a{--foo:red;color:var(--foo)}
Subtotal 3 / 12 9 / 12 3 / 12 4 / 12 10 / 12 10 / 12

font-face

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`@font-face` blocks must be preserved with whitespace stripped. This tests basic whitespace removal without any string or URL optimizations.

Source

@font-face {
  font-display: swap;
  font-weight: 400;
}

Expected

@font-face{font-display:swap;font-weight:400}

Outputs

clean-css

@font-face{font-display:swap;font-weight:400}

csskit

@font-face{font-display:swap;font-weight:400}

cssnano

@font-face{font-display:swap;font-weight:400}

csso

@font-face{font-display:swap;font-weight:400}

esbuild

@font-face{font-display:swap;font-weight:400}

lightningcss

@font-face{font-display:swap;font-weight:400}
0002
Details

`font-family: "Custom"` can be unquoted to `font-family: Custom` when the value is a valid CSS identifier. This does not apply to names that are CSS-wide values or contain special characters.

Source

@font-face {
  font-family: "Custom";
  font-display: swap;
}

Expected

@font-face{font-family:Custom;font-display:swap}

Outputs

clean-css

@font-face{font-family:Custom;font-display:swap}

csskit

@font-face{font-family:"Custom";font-display:swap}

cssnano

@font-face{font-display:swap;font-family:Custom}

csso

@font-face{font-family:"Custom";font-display:swap}

esbuild

@font-face{font-family:Custom;font-display:swap}

lightningcss

@font-face{font-family:Custom;font-display:swap}
0003
Details

Quotes inside `url()` can be removed when the URL contains no special characters (whitespace, parentheses, or single/double quotes). `url("font.woff2")` becomes `url(font.woff2)`.

Source

@font-face {
  font-family: Custom;
  src: url("font.woff2");
}

Expected

@font-face{font-family:Custom;src:url(font.woff2)}

Outputs

clean-css

@font-face{font-family:Custom;src:url("font.woff2")}

csskit

@font-face{font-family:Custom;src:url("font.woff2")}

cssnano

@font-face{font-family:Custom;src:url(font.woff2)}

csso

@font-face{font-family:Custom;src:url(font.woff2)}

esbuild

@font-face{font-family:Custom;src:url(font.woff2)}

lightningcss

@font-face{font-family:Custom;src:url(font.woff2)}
0004
Details

The `format()` function in `@font-face` `src` accepts both string and keyword syntax per CSS Fonts 4. `format("woff2")` and `format(woff2)` are equivalent, so quotes can be safely stripped. Whitespace between `url()` and `format()` can also be removed.

Source

@font-face {
  font-family: Custom;
  src: url(font.woff2) format("woff2");
}

Expected

@font-face{font-family:Custom;src:url(font.woff2)format(woff2)}

Outputs

clean-css

@font-face{font-family:Custom;src:url(font.woff2) format("woff2")}

csskit

@font-face{font-family:Custom;src:url(font.woff2) format("woff2")}

cssnano

@font-face{font-family:Custom;src:url(font.woff2) format("woff2")}

csso

@font-face{font-family:Custom;src:url(font.woff2)format("woff2")}

esbuild

@font-face{font-family:Custom;src:url(font.woff2) format("woff2")}

lightningcss

@font-face{font-family:Custom;src:url(font.woff2)format("woff2")}
0005
Details

Unquoting `"serif"` produces the generic family keyword `serif`, which refers to the browser default serif font -- not a custom font named "serif". Quotes must be preserved when the family name matches a generic keyword.

Source

@font-face {
  font-family: "serif";
  src: url(custom.woff2);
}

Expected

@font-face{font-family:"serif";src:url(custom.woff2)}

Outputs

clean-css

@font-face{font-family:"serif";src:url(custom.woff2)}

csskit

@font-face{font-family:"serif";src:url(custom.woff2)}

cssnano

@font-face{font-family:"serif";src:url(custom.woff2)}

csso

@font-face{font-family:"serif";src:url(custom.woff2)}

esbuild

@font-face{font-family:"serif";src:url(custom.woff2)}

lightningcss

@font-face{font-family:"serif";src:url(custom.woff2)}
0006
Details

`U+0000-00FF` can be shortened to `U+??` using wildcard notation. The `?` replaces trailing hex digits that cover the full 0-F range, and leading zeros in the prefix can be dropped.

Source

@font-face {
  font-family: test;
  src: local(test);
  unicode-range: U+0000-00FF;
}

Expected

@font-face{font-family:test;src:local(test);unicode-range:U+??}

Outputs

clean-css

@font-face{font-family:test;src:local(test);unicode-range:U+0000-00FF}

csskit

@font-face{font-family:test;src:local(test);unicode-range:U0 -0FF}

cssnano

@font-face{font-family:test;src:local(test);unicode-range:u+00??}

csso

@font-face{font-family:test;src:local(test);unicode-range:U+0000-00FF}

esbuild

@font-face{font-family:test;src:local(test);unicode-range:U+0000-00FF}

lightningcss

@font-face{font-family:test;src:local(test);unicode-range:U+??}
Subtotal 3 / 6 2 / 6 3 / 6 3 / 6 4 / 6 5 / 6

gradients

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`linear-gradient(to bottom, ...)` can omit the direction since `to bottom` (180deg) is the default.

Source

a {
  background: linear-gradient(to bottom, red, blue);
}

Expected

a{background:linear-gradient(red,#00f)}

Validate

Outputs

clean-css

a{background:linear-gradient(to bottom,red,#00f)}

csskit

a{background:linear-gradient(to bottom,red,blue)}

cssnano

a{background:linear-gradient(180deg,red,blue)}

csso

a{background:linear-gradient(to bottom,red,#00f)}

esbuild

a{background:linear-gradient(to bottom,red,#00f)}

lightningcss

a{background:linear-gradient(red,#00f)}
0002
Details

`linear-gradient(180deg, ...)` is equivalent to the default direction and can be omitted.

Source

a {
  background: linear-gradient(180deg, red, blue);
}

Expected

a{background:linear-gradient(red,#00f)}

Validate

Outputs

clean-css

a{background:linear-gradient(180deg,red,#00f)}

csskit

a{background:linear-gradient(180deg,red,blue)}

cssnano

a{background:linear-gradient(180deg,red,blue)}

csso

a{background:linear-gradient(180deg,red,#00f)}

esbuild

a{background:linear-gradient(180deg,red,#00f)}

lightningcss

a{background:linear-gradient(red,#00f)}
0003
Details

`to right` can be replaced with `90deg` which is shorter.

Source

a {
  background: linear-gradient(to right, red, blue);
}

Expected

a{background:linear-gradient(90deg,red,#00f)}

Validate

Outputs

clean-css

a{background:linear-gradient(to right,red,#00f)}

csskit

a{background:linear-gradient(to right,red,blue)}

cssnano

a{background:linear-gradient(90deg,red,blue)}

csso

a{background:linear-gradient(to right,red,#00f)}

esbuild

a{background:linear-gradient(to right,red,#00f)}

lightningcss

a{background:linear-gradient(90deg,red,#00f)}
0004
Details

`0%` on the first stop and `100%` on the last stop are defaults and can be removed.

Source

a {
  background: linear-gradient(red 0%, blue 100%);
}

Expected

a{background:linear-gradient(red,#00f)}

Validate

Outputs

clean-css

a{background:linear-gradient(red 0,#00f 100%)}

csskit

a{background:linear-gradient(red 0%,blue 100%)}

cssnano

a{background:linear-gradient(red,blue)}

csso

a{background:linear-gradient(red 0,#00f 100%)}

esbuild

a{background:linear-gradient(red,#00f)}

lightningcss

a{background:linear-gradient(red 0%,#00f 100%)}
0005
Details

`ellipse at center` is the default for radial-gradient and can be omitted entirely.

Source

a {
  background: radial-gradient(ellipse at center, red, blue);
}

Expected

a{background:radial-gradient(red,#00f)}

Validate

Outputs

clean-css

a{background:radial-gradient(ellipse at center,red,#00f)}

csskit

a{background:radial-gradient(ellipse at center,red,blue)}

cssnano

a{background:radial-gradient(ellipse at center,red,blue)}

csso

a{background:radial-gradient(ellipse at center,red,#00f)}

esbuild

a{background:radial-gradient(ellipse at center,red,#00f)}

lightningcss

a{background:radial-gradient(red,#00f)}
Subtotal 0 / 5 0 / 5 0 / 5 0 / 5 1 / 5 4 / 5

import

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`@import url("foo.css")` can be shortened to `@import"foo.css"`. The CSS spec allows both forms and the string form is always shorter. No space needed between `@import` and the string token.

Source

@import url("foo.css");

Expected

@import"foo.css";

Outputs

clean-css

@import url(foo.css);

csskit

@import url("foo.css");

cssnano

@import url("foo.css");

csso

@import url(foo.css);

esbuild

@import"foo.css";

lightningcss

@import "foo.css";
0002
Details

`@import url("foo.css") screen` shortens to `@import"foo.css"screen`. The string form removes the url() wrapper and no space is needed between the closing quote and the media query keyword.

Source

@import url("foo.css") screen;

Expected

@import"foo.css"screen;

Outputs

clean-css

@import url(foo.css) screen;

csskit

@import url("foo.css")screen;

cssnano

@import url("foo.css") screen;

csso

@import url(foo.css)screen;

esbuild

@import"foo.css"screen;

lightningcss

@import "foo.css" screen;
0003
Details

`@import url(foo.css)` shortens to `@import"foo.css"`. Even when the url() value is already unquoted, the string form is shorter because it removes the `url(` and `)` wrapper (5 chars) and adds only 2 quote chars.

Source

@import url(foo.css);

Expected

@import"foo.css";

Outputs

clean-css

@import url(foo.css);

csskit

@import url(foo.css);

cssnano

@import url(foo.css);

csso

@import url(foo.css);

esbuild

@import"foo.css";

lightningcss

@import "foo.css";
0004
Details

`@import url("foo.css") layer` shortens to `@import"foo.css"layer`. The layer keyword follows the import URL and no space is needed after the closing quote.

Source

@import url("foo.css") layer;

Expected

@import"foo.css"layer;

Outputs

clean-css

@import url(foo.css) layer;

csskit

@import url("foo.css")layer;

cssnano

@import url("foo.css") layer;

csso

@import url(foo.css)layer;

esbuild

@import"foo.css"layer;

lightningcss

@import "foo.css" layer;
0005
Details

`@import url("foo.css") layer(base) supports(foo: bar)` removes url() wrapper, removes spaces between the closing `)` and next keyword/function, and removes whitespace inside the supports() condition. Uses an unknown property so minifiers cannot elide the supports() condition.

Source

@import url("foo.css") layer(base) supports(foo: bar);

Expected

@import"foo.css"layer(base)supports(foo:bar);

Outputs

clean-css

@import url(foo.css) layer(base) supports(foo: bar);

csskit

@import url("foo.css")layer(base)supports(foo:bar);

cssnano

@import url("foo.css") layer(base) supports(foo: bar);

csso

@import url("foo.css") layer(base) supports(foo: bar);

esbuild

@import"foo.css"layer(base) supports(foo: bar);

lightningcss

@import "foo.css" layer(base) supports(foo:bar);
Subtotal 0 / 5 0 / 5 0 / 5 0 / 5 4 / 5 0 / 5

keyframes

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

The `100%` keyframe stop can be replaced with the shorter `to` keyword.

Source

@keyframes fade {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

Expected

@keyframes fade{0%{opacity:0}to{opacity:1}}

Outputs

clean-css

@keyframes fade{0%{opacity:0}100%{opacity:1}}

csskit

@keyframes fade{0%{opacity:0}100%{opacity:1}}

cssnano

@keyframes fade{0%{opacity:0}to{opacity:1}}

csso

@keyframes fade{0%{opacity:0}to{opacity:1}}

esbuild

@keyframes fade{0%{opacity:0}to{opacity:1}}

lightningcss

@keyframes fade{0%{opacity:0}to{opacity:1}}
0002
Details

`from` becomes `0%` and `100%` becomes `to` since these are shorter representations.

Source

@keyframes fade {
  from {
    opacity: 0;
  }
  50% {
    opacity: 0.5;
  }
  100% {
    opacity: 1;
  }
}

Expected

@keyframes fade{0%{opacity:0}50%{opacity:.5}to{opacity:1}}

Outputs

clean-css

@keyframes fade{from{opacity:0}50%{opacity:.5}100%{opacity:1}}

csskit

@keyframes fade{from{opacity:0}50%{opacity:.5}100%{opacity:1}}

cssnano

@keyframes fade{0%{opacity:0}50%{opacity:.5}to{opacity:1}}

csso

@keyframes fade{0%{opacity:0}50%{opacity:.5}to{opacity:1}}

esbuild

@keyframes fade{0%{opacity:0}50%{opacity:.5}to{opacity:1}}

lightningcss

@keyframes fade{0%{opacity:0}50%{opacity:.5}to{opacity:1}}
Subtotal 0 / 2 0 / 2 2 / 2 2 / 2 2 / 2 2 / 2

layer

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Duplicate `@layer` declarations can be deduplicated since layer order is established by first occurrence.

Source

@layer utilities;
@layer utilities;
a {
  color: red;
}

Expected

@layer utilities;a{color:red}

Outputs

clean-css

@layer utilities;@layer utilities;a{color:red}

csskit

@layer utilities;@layer utilities;a{color:red}

cssnano

@layer utilities;@layer utilities;a{color:red}

csso

@layer utilities;@layer utilities;a{color:red}

esbuild

@layer utilities;@layer utilities;a{color:red}

lightningcss

@layer utilities;a{color:red}
0002
Details

Layer ordering is determined by first appearance of the layer name, not by physical position of rule blocks. Two `@layer base` blocks separated by `@layer utilities` can be merged because layer precedence is unchanged.

Source

@layer base {
  a {
    color: blue;
  }
}

@layer utilities {
  a {
    color: red;
  }
}

@layer base {
  a {
    font-weight: bold;
  }
}

Expected

@layer base{a{color:#00f;font-weight:700}}@layer utilities{a{color:red}}

Outputs

clean-css

@layer base{a{color:#00f;font-weight:700}}@layer utilities{a{color:red}}

csskit

@layer base{a{color:blue}}@layer utilities{a{color:red}}@layer base{a{font-weight:bold}}

cssnano

@layer base{a{color:blue}}@layer utilities{a{color:red}}@layer base{a{font-weight:700}}

csso

@layer base{a{color:#00f}}@layer utilities{a{color:red}}@layer base{a{font-weight:700}}

esbuild

@layer base{a{color:#00f}}@layer utilities{a{color:red}}@layer base{a{font-weight:700}}

lightningcss

@layer base{a{color:#00f;font-weight:700}}@layer utilities{a{color:red}}
Subtotal 1 / 2 0 / 2 0 / 2 0 / 2 0 / 2 2 / 2

media

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Strip whitespace inside a basic `@media` block with nested rules.

Source

@media screen {
  a {
    color: red;
  }
}

Expected

@media screen{a{color:red}}

Outputs

clean-css

@media screen{a{color:red}}

csskit

@media screen{a{color:red}}

cssnano

@media screen{a{color:red}}

csso

@media screen{a{color:red}}

esbuild

@media screen{a{color:red}}

lightningcss

@media screen{a{color:red}}
0002
Details

Nested `@media` rules should be minified but NOT flattened into a single rule. Merging can change cascade behavior in edge cases.

Source

@media screen {
  @media (width >= 768px) {
    a {
      color: red;
    }
  }
}

Expected

@media screen{@media (width>=768px){a{color:red}}}

Outputs

clean-css

@media screen{@media (width >= 768px){a{color:red}}}

csskit

@media screen{@media(width>=768px){a{color:red}}}

cssnano

@media screen{@media (width >= 768px){a{color:red}}}

csso

esbuild

@media screen{@media(width>=768px){a{color:red}}}

lightningcss

@media screen{@media (width>=768px){a{color:red}}}
0003
Details

`@media all and (condition)` is equivalent to `@media (condition)`.

Source

@media all and (width >= 500px) {
  a {
    color: red;
  }
}

Expected

@media (width>=500px){a{color:red}}

Outputs

clean-css

@media all and (width >= 500px){a{color:red}}

csskit

@media all and (width>=500px){a{color:red}}

cssnano

@media (width >= 500px){a{color:red}}

csso

esbuild

@media all and (width>=500px){a{color:red}}

lightningcss

@media (width>=500px){a{color:red}}
0004
Details

Adjacent @media blocks with the same query should be merged into one.

Source

@media screen and (width >= 768px) {
  a {
    color: red;
  }
}
@media screen and (width >= 768px) {
  b {
    margin: 0;
  }
}

Expected

@media screen and (width>=768px){a{color:red}b{margin:0}}

Outputs

clean-css

@media screen and (width >= 768px){a{color:red}b{margin:0}}

csskit

@media screen and (width>=768px){a{color:red}}@media screen and (width>=768px){b{margin:0}}

cssnano

@media screen and (width >= 768px){a{color:red}b{margin:0}}

csso

esbuild

@media screen and (width>=768px){a{color:red}}@media screen and (width>=768px){b{margin:0}}

lightningcss

@media screen and (width>=768px){a{color:red}b{margin:0}}
0005
Details

`(min-width: 768px)` can be shortened to `(width>=768px)` using Media Queries Level 4 range syntax.

Source

@media (min-width: 768px) {
  a {
    color: red;
  }
}

Expected

@media (width>=768px){a{color:red}}

Outputs

clean-css

@media (min-width:768px){a{color:red}}

csskit

@media(min-width:768px){a{color:red}}

cssnano

@media (min-width:768px){a{color:red}}

csso

@media (min-width:768px){a{color:red}}

esbuild

@media(min-width:768px){a{color:red}}

lightningcss

@media (width>=768px){a{color:red}}
0006
Details

`(max-width: 1024px)` can be shortened to `(width<=1024px)` using Media Queries Level 4 range syntax.

Source

@media (max-width: 1024px) {
  a {
    color: red;
  }
}

Expected

@media (width<=1024px){a{color:red}}

Outputs

clean-css

@media (max-width:1024px){a{color:red}}

csskit

@media(max-width:1024px){a{color:red}}

cssnano

@media (max-width:1024px){a{color:red}}

csso

@media (max-width:1024px){a{color:red}}

esbuild

@media(max-width:1024px){a{color:red}}

lightningcss

@media (width<=1024px){a{color:red}}
0007
Details

`(min-height: 600px)` can be shortened to `(height>=600px)` using Media Queries Level 4 range syntax.

Source

@media (min-height: 600px) {
  a {
    color: red;
  }
}

Expected

@media (height>=600px){a{color:red}}

Outputs

clean-css

@media (min-height:600px){a{color:red}}

csskit

@media(min-height:600px){a{color:red}}

cssnano

@media (min-height:600px){a{color:red}}

csso

@media (min-height:600px){a{color:red}}

esbuild

@media(min-height:600px){a{color:red}}

lightningcss

@media (height>=600px){a{color:red}}
0008
Details

`(max-height: 900px)` can be shortened to `(height<=900px)` using Media Queries Level 4 range syntax.

Source

@media (max-height: 900px) {
  a {
    color: red;
  }
}

Expected

@media (height<=900px){a{color:red}}

Outputs

clean-css

@media (max-height:900px){a{color:red}}

csskit

@media(max-height:900px){a{color:red}}

cssnano

@media (max-height:900px){a{color:red}}

csso

@media (max-height:900px){a{color:red}}

esbuild

@media(max-height:900px){a{color:red}}

lightningcss

@media (height<=900px){a{color:red}}
0009
Details

`(min-width: 768px) and (max-width: 1024px)` can be collapsed into a single range condition `(768px<=width<=1024px)`, eliminating the `and` keyword and one pair of parentheses.

Source

@media (min-width: 768px) and (max-width: 1024px) {
  a {
    color: red;
  }
}

Expected

@media (768px<=width<=1024px){a{color:red}}

Outputs

clean-css

@media (min-width:768px) and (max-width:1024px){a{color:red}}

csskit

@media(min-width:768px)and (max-width:1024px){a{color:red}}

cssnano

@media (min-width:768px) and (max-width:1024px){a{color:red}}

csso

@media (min-width:768px) and (max-width:1024px){a{color:red}}

esbuild

@media(min-width:768px)and (max-width:1024px){a{color:red}}

lightningcss

@media (width>=768px) and (width<=1024px){a{color:red}}
Subtotal 1 / 9 1 / 9 1 / 9 1 / 9 1 / 9 8 / 9

merging

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Adjacent rules sharing the same selector can be merged into one rule with combined declarations.

Source

a {
  color: red;
}
a {
  font-size: 16px;
}

Expected

a{color:red;font-size:16px}

Outputs

clean-css

a{color:red;font-size:16px}

csskit

a{color:red}a{font-size:16px}

cssnano

a{color:red;font-size:16px}

csso

a{color:red;font-size:16px}

esbuild

a{color:red}a{font-size:16px}

lightningcss

a{color:red;font-size:16px}
0002
Details

Adjacent rules with identical declarations can merge their selectors into a comma-separated list.

Source

.a {
  color: red;
}
.b {
  color: red;
}

Expected

.a,.b{color:red}

Outputs

clean-css

.a,.b{color:red}

csskit

.a{color:red}.b{color:red}

cssnano

.a,.b{color:red}

csso

.a,.b{color:red}

esbuild

.a,.b{color:red}

lightningcss

.a,.b{color:red}
0003
Details

Adjacent @media rules with the same query can be merged into a single @media block.

Source

@media screen {
  a {
    color: red;
  }
}
@media screen {
  b {
    margin: 0;
  }
}

Expected

@media screen{a{color:red}b{margin:0}}

Outputs

clean-css

@media screen{a{color:red}b{margin:0}}

csskit

@media screen{a{color:red}}@media screen{b{margin:0}}

cssnano

@media screen{a{color:red}b{margin:0}}

csso

@media screen{a{color:red}b{margin:0}}

esbuild

@media screen{a{color:red}}@media screen{b{margin:0}}

lightningcss

@media screen{a{color:red}b{margin:0}}
0004
Details

An @media rule with no content should be removed entirely.

Source

@media screen {
}
a {
  color: red;
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

@media screen{}a{color:red}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0005
Details

When adjacent rules share some declarations, the common part can be factored out.

Source

a {
  color: red;
}
b {
  color: red;
  font-size: 16px;
}

Expected

a,b{color:red}b{font-size:16px}

Outputs

clean-css

a{color:red}b{color:red;font-size:16px}

csskit

a{color:red}b{color:red;font-size:16px}

cssnano

a,b{color:red}b{font-size:16px}

csso

a,b{color:red}b{font-size:16px}

esbuild

a{color:red}b{color:red;font-size:16px}

lightningcss

a{color:red}b{color:red;font-size:16px}
0006
Details

When two @keyframes share the same name, only the last definition is kept.

Source

@keyframes fade {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
@keyframes fade {
  0% {
    opacity: 0.5;
  }
  100% {
    opacity: 1;
  }
}

Expected

@keyframes fade{0%{opacity:.5}to{opacity:1}}

Outputs

clean-css

@keyframes fade{0%{opacity:0;opacity:.5}100%{opacity:1}}

csskit

@keyframes fade{0%{opacity:0}100%{opacity:1}}@keyframes fade{0%{opacity:.5}100%{opacity:1}}

cssnano

@keyframes fade{0%{opacity:.5}to{opacity:1}}

csso

@keyframes fade{0%{opacity:.5}to{opacity:1}}

esbuild

@keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:.5}to{opacity:1}}

lightningcss

@keyframes fade{0%{opacity:.5}to{opacity:1}}
0007
Details

Merging `.foo{color:red}` and `.bar{all:unset;color:red}` into `.bar,.foo{color:red}.bar{all:unset}` is unsafe because `all:unset` resets `color`, so the merged color declaration would be overridden.

Source

.foo {
  color: red;
}

.bar {
  all: unset;
  color: red;
}

Expected

.foo{color:red}.bar{all:unset;color:red}

Validate

Outputs

clean-css

.foo{color:red}.bar{all:unset;color:red}

csskit

.foo{color:red}.bar{all:unset;color:red}

cssnano

.foo{color:red}.bar{all:unset;color:red}

csso

.bar,.foo{color:red}.bar{all:unset}

esbuild

.foo{color:red}.bar{all:unset;color:red}

lightningcss

.foo{color:red}.bar{all:unset;color:red}
0008
Details

Merging two identical `@media (width >= 1px)` blocks that have intervening rules between them changes the cascade order. The intervening `a{color:green}` must remain between the two @media blocks so it correctly overrides the first block's `color:red`.

Source

@media (width >= 1px) {
  a {
    color: red;
  }
}

a {
  color: green;
}

@media (width >= 1px) {
  a {
    background: #0b0;
  }
}

Expected

@media (width>=1px){a{color:red}}a{color:green}@media (width>=1px){a{background:#0b0}}

Validate

Outputs

clean-css

@media (width >= 1px){a{color:red;background:#0b0}}a{color:green}

csskit

@media(width>=1px){a{color:red}}a{color:green}@media(width>=1px){a{background:#0b0}}

cssnano

@media (width >= 1px){a{color:red}}a{color:green}@media (width >= 1px){a{background:#0b0}}

csso

a{color:green}

esbuild

@media(width>=1px){a{color:red}}a{color:green}@media(width>=1px){a{background:#0b0}}

lightningcss

@media (width>=1px){a{color:red}}a{color:green}@media (width>=1px){a{background:#0b0}}
0009
Details

Merging `.foo{color:red}` and `.bar{all:initial;color:red}` into `.bar,.foo{color:red}.bar{all:initial}` is unsafe because `all:initial` resets `color` to its initial value, so the merged color declaration would be overridden.

Source

.foo {
  color: red;
}

.bar {
  all: initial;
  color: red;
}

Expected

.foo{color:red}.bar{all:initial;color:red}

Validate

Outputs

clean-css

.foo{color:red}.bar{all:initial;color:red}

csskit

.foo{color:red}.bar{all:initial;color:red}

cssnano

.foo{color:red}.bar{all:initial;color:red}

csso

.bar,.foo{color:red}.bar{all:initial}

esbuild

.foo{color:red}.bar{all:initial;color:red}

lightningcss

.foo{color:red}.bar{all:initial;color:red}
0010
Details

Merging `.foo{color:red}` and `.bar{all:revert-layer;color:red}` into `.bar,.foo{color:red}.bar{all:revert-layer}` is unsafe because `all:revert-layer` rolls back `color` to the previous cascade layer, so the merged color declaration would be overridden.

Source

.foo {
  color: red;
}

.bar {
  all: revert-layer;
  color: red;
}

Expected

.foo{color:red}.bar{all:revert-layer;color:red}

Validate

Outputs

clean-css

.foo{color:red}.bar{all:revert-layer;color:red}

csskit

.foo{color:red}.bar{all:revert-layer;color:red}

cssnano

.foo{color:red}.bar{all:revert-layer;color:red}

csso

.bar,.foo{color:red}.bar{all:revert-layer}

esbuild

.foo{color:red}.bar{all:revert-layer;color:red}

lightningcss

.foo{color:red}.bar{all:revert-layer;color:red}
Subtotal 7 / 10 3 / 10 9 / 10 6 / 10 5 / 10 9 / 10

nesting

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

A nested style rule inside a parent rule should be minified in place, preserving the nesting structure while removing whitespace.

Source

.foo {
  color: red;
  .bar {
    margin: 0;
  }
}

Expected

.foo{color:red;.bar{margin:0}}

Outputs

clean-css

.foo{color:red}

csskit

.foo{color:red;.bar{margin:0}}

cssnano

.foo{color:red;.bar{margin:0}}

csso

.foo{color:red}

esbuild

.foo{color:red;.bar{margin:0}}

lightningcss

.foo{color:red;& .bar{margin:0}}
0002
Details

`&:hover` inside a parent rule must be preserved as-is. The `&` refers to the parent selector and appending `:hover` creates a compound match.

Source

a {
  color: red;
  &:hover {
    margin: 0;
  }
}

Expected

a{color:red;&:hover{margin:0}}

Outputs

clean-css

a{color:red}

csskit

a{color:red;&:hover{margin:0}}

cssnano

a{color:red;&:hover{margin:0}}

csso

a{color:red}

esbuild

a{color:red;&:hover{margin:0}}

lightningcss

a{color:red;&:hover{margin:0}}
0003
Details

`&.bar` refines the parent selector, matching elements that match both `.foo` and `.bar`. The compound form must be preserved during minification.

Source

.foo {
  color: red;
  &.bar {
    margin: 0;
  }
}

Expected

.foo{color:red;&.bar{margin:0}}

Outputs

clean-css

.foo{color:red}

csskit

.foo{color:red;&.bar{margin:0}}

cssnano

.foo{color:red;&.bar{margin:0}}

csso

.foo{color:red}

esbuild

.foo{color:red;&.bar{margin:0}}

lightningcss

.foo{color:red;&.bar{margin:0}}
0004
Details

`> .bar` inside a parent rule uses a relative selector (implicit `&`). The child combinator form without an explicit `&` must be preserved.

Source

.foo {
  color: red;
  > .bar {
    margin: 0;
  }
}

Expected

.foo{color:red;>.bar{margin:0}}

Outputs

clean-css

.foo{color:red}

csskit

.foo{color:red;>.bar{margin:0}}

cssnano

.foo{color:red;>.bar{margin:0}}

csso

.foo{color:red}

esbuild

.foo{color:red;>.bar{margin:0}}

lightningcss

.foo{color:red;&>.bar{margin:0}}
0005
Details

Three levels of nesting (`figure > figcaption > p`) must all be preserved and minified correctly without flattening.

Source

figure {
  margin: 0;
  > figcaption {
    color: red;
    > p {
      font-size: .9rem;
    }
  }
}

Expected

figure{margin:0;>figcaption{color:red;>p{font-size:.9rem}}}

Outputs

clean-css

figure{margin:0}

csskit

figure{margin:0;>figcaption{color:red;>p{font-size:.9rem}}}

cssnano

figure{margin:0;>figcaption{color:red;>p{font-size:.9rem}}}

csso

figure{margin:0}

esbuild

figure{margin:0;>figcaption{color:red;>p{font-size:.9rem}}}

lightningcss

figure{margin:0;&>figcaption{color:red;&>p{font-size:.9rem}}}
0006
Details

A conditional `@media` rule nested inside a style rule should be minified in place. The space after `@media` can be removed when the condition starts with `(`.

Source

.foo {
  display: grid;
  @media (width >= 768px) {
    grid-auto-flow: column;
  }
}

Expected

.foo{display:grid;@media(width>=768px){grid-auto-flow:column}}

Outputs

clean-css

.foo{display:grid}

csskit

.foo{display:grid;@media(width>=768px){grid-auto-flow:column}}

cssnano

.foo{display:grid;@media (width >= 768px){grid-auto-flow:column}}

csso

.foo{display:grid}

esbuild

.foo{display:grid;@media(width>=768px){grid-auto-flow:column}}

lightningcss

.foo{display:grid;@media (width>=768px){grid-auto-flow:column}}
0007
Details

`& .bar` is equivalent to `.bar` in a nesting context because a nested selector without `&` implies a descendant relationship. The explicit `&` is redundant and can be removed.

Source

.foo {
  color: red;
  & .bar {
    margin: 0;
  }
}

Expected

.foo{color:red;.bar{margin:0}}

Outputs

clean-css

.foo{color:red}

csskit

.foo{color:red;&.bar{margin:0}}

cssnano

.foo{color:red;& .bar{margin:0}}

csso

.foo{color:red}

esbuild

.foo{color:red;.bar{margin:0}}

lightningcss

.foo{color:red;& .bar{margin:0}}
0008
Details

`&&` equals `.foo.foo`, doubling specificity. Must not collapse to a single `&`.

Source

.foo {
  color: red;
  && {
    font-size: 16px;
  }
}

Expected

.foo{color:red;&&{font-size:16px}}

Outputs

clean-css

.foo{color:red}

csskit

.foo{color:red;&&{font-size:16px}}

cssnano

.foo{color:red;&&{font-size:16px}}

csso

.foo{color:red}

esbuild

.foo{color:red;&&{font-size:16px}}

lightningcss

.foo{color:red;&&{font-size:16px}}
0009
Details

`.parent &` matches `.parent .foo` -- the `&` is not at the start of the selector. This form must be preserved; it cannot be simplified.

Source

.foo {
  color: red;
  .parent & {
    margin: 0;
  }
}

Expected

.foo{color:red;.parent &{margin:0}}

Outputs

clean-css

.foo{color:red}

csskit

.foo{color:red;.parent&{margin:0}}

cssnano

.foo{color:red;.parent &{margin:0}}

csso

.foo{color:red}

esbuild

.foo{color:red;.parent &{margin:0}}

lightningcss

.foo{color:red;.parent &{margin:0}}
0010
Details

Three flat rules sharing `.nav` as parent combine into a single nested rule, eliminating repeated selector text.

Source

.nav {
  display: flex;
}
.nav a {
  color: red;
}
.nav a:hover {
  margin: 0;
}

Expected

.nav{display:flex;a{color:red;&:hover{margin:0}}}

Outputs

clean-css

.nav{display:flex}.nav a{color:red}.nav a:hover{margin:0}

csskit

.nav{display:flex}.nav a{color:red}.nav a:hover{margin:0}

cssnano

.nav{display:flex}.nav a{color:red}.nav a:hover{margin:0}

csso

.nav{display:flex}.nav a{color:red}.nav a:hover{margin:0}

esbuild

.nav{display:flex}.nav a{color:red}.nav a:hover{margin:0}

lightningcss

.nav{display:flex}.nav a{color:red}.nav a:hover{margin:0}
0011
Details

`a { ... } a:hover { ... }` is shorter when nested as `a{...;&:hover{...}}`, eliminating the repeated `a` selector.

Source

a {
  color: red;
}
a:hover {
  margin: 0;
}

Expected

a{color:red;&:hover{margin:0}}

Outputs

clean-css

a{color:red}a:hover{margin:0}

csskit

a{color:red}a:hover{margin:0}

cssnano

a{color:red}a:hover{margin:0}

csso

a{color:red}a:hover{margin:0}

esbuild

a{color:red}a:hover{margin:0}

lightningcss

a{color:red}a:hover{margin:0}
0012
Details

`.card { ... } .card > .title { ... }` is shorter when nested using a relative child selector: `.card{...;>.title{...}}`.

Source

.card {
  padding: 1rem;
}
.card > .title {
  color: red;
}

Expected

.card{padding:1rem;>.title{color:red}}

Outputs

clean-css

.card{padding:1rem}.card>.title{color:red}

csskit

.card{padding:1rem}.card>.title{color:red}

cssnano

.card{padding:1rem}.card>.title{color:red}

csso

.card{padding:1rem}.card>.title{color:red}

esbuild

.card{padding:1rem}.card>.title{color:red}

lightningcss

.card{padding:1rem}.card>.title{color:red}
0013
Details

When a parent rule has no declarations of its own, the nested child can be flattened into a single descendant selector. `a { b { color: red } }` becomes `a b{color:red}`, eliminating the empty wrapper.

Source

a {
  b {
    color: red;
  }
}

Expected

a b{color:red}

Outputs

clean-css

csskit

a{b{color:red}}

cssnano

a{b{color:red}}

csso

esbuild

a{b{color:red}}

lightningcss

a{& b{color:red}}
0014
Details

`a { > b { color: red } }` can be flattened to `a>b{color:red}` when the parent has no own declarations. The relative child selector resolves to a direct child combinator.

Source

a {
  > b {
    color: red;
  }
}

Expected

a>b{color:red}

Outputs

clean-css

csskit

a{>b{color:red}}

cssnano

a{>b{color:red}}

csso

esbuild

a{>b{color:red}}

lightningcss

a{&>b{color:red}}
0015
Details

`a { + b { color: red } }` can be flattened to `a+b{color:red}`. The relative next-sibling selector resolves to the adjacent sibling combinator when the parent wrapper has no declarations.

Source

a {
  + b {
    color: red;
  }
}

Expected

a+b{color:red}

Outputs

clean-css

csskit

a{+b{color:red}}

cssnano

a{+b{color:red}}

csso

esbuild

a{+b{color:red}}

lightningcss

a{&+b{color:red}}
0016
Details

`a { ~ b { color: red } }` can be flattened to `a~b{color:red}`. The relative subsequent-sibling selector resolves to the general sibling combinator when the parent has no own declarations.

Source

a {
  ~ b {
    color: red;
  }
}

Expected

a~b{color:red}

Outputs

clean-css

csskit

a{~b{color:red}}

cssnano

a{~b{color:red}}

csso

esbuild

a{~b{color:red}}

lightningcss

a{&~b{color:red}}
0017
Details

When a parent has no own declarations but multiple nested children, keeping the nesting is shorter than flattening because the parent selector only appears once instead of being repeated for each child rule.

Source

nav {
  a {
    color: red;
  }
  > .icon {
    width: 1rem;
  }
  + footer {
    margin: 0;
  }
}

Expected

nav{a{color:red}>.icon{width:1rem}+footer{margin:0}}

Outputs

clean-css

csskit

nav{a{color:red}>.icon{width:1rem}+footer{margin:0}}

cssnano

nav{a{color:red}>.icon{width:1rem}+footer{margin:0}}

csso

esbuild

nav{a{color:red}>.icon{width:1rem}+footer{margin:0}}

lightningcss

nav{& a{color:red}&>.icon{width:1rem}&+footer{margin:0}}
0018
Details

`a { &:hover { color: red } }` can be flattened to `a:hover{color:red}` when the parent has no own declarations. The `&` resolves to the parent selector, producing the compound `a:hover`.

Source

a {
  &:hover {
    color: red;
  }
}

Expected

a:hover{color:red}

Outputs

clean-css

csskit

a{&:hover{color:red}}

cssnano

a{&:hover{color:red}}

csso

esbuild

a{&:hover{color:red}}

lightningcss

a{&:hover{color:red}}
0019
Details

Two identical `.bar` rules nested inside `.foo` should collapse to one, then flatten to `.foo .bar{...}` since there are no other rules in the parent.

Source

.foo {
  .bar {
    color: red;
    font-size: 1rem;
  }
  .bar {
    color: red;
    font-size: 1rem;
  }
}

Expected

.foo .bar{color:red;font-size:1rem}

Outputs

clean-css

.foo{font-size:1rem}.bar{color:red;font-size:1rem}

csskit

.foo{.bar{color:red;font-size:1rem}.bar{color:red;font-size:1rem}}

cssnano

.foo{.bar{color:red;font-size:1rem}}

csso

esbuild

.foo{.bar{color:red;font-size:1rem}}

lightningcss

.foo{& .bar{color:red;font-size:1rem}}
Subtotal 0 / 19 8 / 19 8 / 19 0 / 19 10 / 19 4 / 19

page

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Basic whitespace removal in a `@page` rule. The space between `@page` and `{` can be removed, and internal whitespace is minified as usual.

Source

@page {
  margin: 1in;
}

Expected

@page{margin:1in}

Outputs

clean-css

@page{margin:1in}

csskit

@page{margin:1in}

cssnano

@page{margin:1in}

csso

@page{margin:1in}

esbuild

@page{margin:1in}

lightningcss

@page{margin:1in}
0002
Details

`@page :first` targets the first page. Whitespace between the pseudo-class and the opening brace can be removed.

Source

@page :first {
  margin: 2in;
}

Expected

@page:first{margin:2in}

Outputs

clean-css

@page :first{margin:2in}

csskit

@page:first{margin:2in}

cssnano

@page :first{margin:2in}

csso

@page :first{margin:2in}

esbuild

@page :first{margin:2in}

lightningcss

@page:first{margin:2in}
0003
Details

An empty `@page {}` rule with no declarations can be removed entirely.

Source

@page {
}

Expected

Outputs

clean-css

csskit

@page{}

cssnano

csso

esbuild

lightningcss

@page{}
0004
Details

`@page` can contain margin at-rules like `@top-center`. Whitespace is removed inside both the outer and nested at-rule blocks.

Source

@page {
  @top-center {
    content: "Title";
  }
}

Expected

@page{@top-center{content:"Title"}}

Outputs

clean-css

@page{@top-center{content:"Title"}}

csskit

@page{@top-center{content:"Title"}}

cssnano

@page{@top-center{content:"Title"}}

csso

@page{@top-center{content:"Title"}}

esbuild

@page{@top-center{content:"Title"}}

lightningcss

@page{@top-center{content:"Title"}}
Subtotal 3 / 4 3 / 4 3 / 4 3 / 4 3 / 4 3 / 4

property

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Remove whitespace inside `@property` while preserving the descriptor values. The `syntax` string and `initial-value` must not be altered.

Source

@property --brand-color {
  syntax: "<color>";
  inherits: false;
  initial-value: #000;
}

Expected

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}

Outputs

clean-css

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}

csskit

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}

cssnano

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}

csso

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}

esbuild

@property --brand-color{syntax: "<color>"; inherits: false; initial-value: #000;}

lightningcss

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}
0002
Details

All three descriptors (`syntax`, `inherits`, `initial-value`) are required and must not be removed or altered. The `syntax` string quotes are required. Changing `inherits` silently breaks inheritance behavior.

Source

@property --size {
  syntax: "<length>";
  inherits: true;
  initial-value: 16px;
}

Expected

@property --size{syntax:"<length>";inherits:true;initial-value:16px}

Outputs

clean-css

@property --size{syntax:"<length>";inherits:true;initial-value:16px}

csskit

@property --size{syntax:"<length>";inherits:true;initial-value:16px}

cssnano

@property --size{syntax:"<length>";inherits:true;initial-value:16px}

csso

@property --size{syntax:"<length>";inherits:true;initial-value:16px}

esbuild

@property --size{syntax: "<length>"; inherits: true; initial-value: 16px;}

lightningcss

@property --size{syntax:"<length>";inherits:true;initial-value:16px}
Subtotal 2 / 2 2 / 2 2 / 2 2 / 2 0 / 2 2 / 2

scope

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Basic whitespace removal in `@scope`. The space between `@scope` and the opening parenthesis can be removed.

Source

@scope (.card) {
  .title {
    color: red;
  }
}

Expected

@scope(.card){.title{color:red}}

Outputs

clean-css

csskit

@scope (.card){.title{color:red}}

cssnano

@scope (.card){.title{color:red}}

csso

@scope (.card){.title{color:red}}

esbuild

@scope(.card){.title{color:red}}

lightningcss

@scope(.card){.title{color:red}}
0002
Details

`@scope (.card) to (.card .content)` uses a scope limit. Whitespace around the parentheses can be removed but the space between `to` and `(` must be preserved to avoid `to(` being tokenized as a function token.

Source

@scope (.card) to (.card .content) {
  a {
    color: blue;
  }
}

Expected

@scope(.card)to (.card .content){a{color:blue}}

Outputs

clean-css

csskit

@scope (.card)to (.card .content){a{color:blue}}

cssnano

@scope (.card) to (.card .content){a{color:blue}}

csso

@scope (.card) to (.card .content){a{color:#00f}}

esbuild

@scope(.card)to (.card .content){a{color:#00f}}

lightningcss

@scope(.card) to (.card .content){a{color:#00f}}
0003
Details

An empty `@scope` block with no rules can be removed entirely.

Source

@scope (.card) {
}

Expected

Outputs

clean-css

csskit

@scope (.card){}

cssnano

csso

esbuild

@scope(.card){}

lightningcss

@scope(.card){}
Subtotal 1 / 3 0 / 3 1 / 3 1 / 3 1 / 3 1 / 3

selectors

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`2n+1` is the definition of `odd`. The keyword form is shorter.

Source

a:nth-child(2n + 1) {
  color: red;
}

Expected

a:nth-child(odd){color:red}

Outputs

clean-css

a:nth-child(odd){color:red}

csskit

a:nth-child(2n+ 1){color:red}

cssnano

a:nth-child(odd){color:red}

csso

a:nth-child(2n+1){color:red}

esbuild

a:nth-child(odd){color:red}

lightningcss

a:nth-child(odd){color:red}
0002
Details

`2n+0` simplifies to `2n`. The `+0` offset is redundant.

Source

a:nth-child(2n + 0) {
  color: red;
}

Expected

a:nth-child(2n){color:red}

Outputs

clean-css

a:nth-child(2n+0){color:red}

csskit

a:nth-child(2n+ 0){color:red}

cssnano

a:nth-child(2n+0){color:red}

csso

a:nth-child(2n+0){color:red}

esbuild

a:nth-child(2n){color:red}

lightningcss

a:nth-child(2n){color:red}
0003 ERR
Details

`+5` is the same as `5`. The redundant positive sign should be stripped.

Source

a:nth-child(+ 5) {
  color: red;
}

Expected

a:nth-child(5){color:red}

Outputs

clean-css

a:nth-child(+5){color:red}

csskit

a:nth-child(+ 5){color:red}

cssnano

a:nth-child(+5){color:red}

csso

esbuild

a:nth-child(+ 5){color:red}

lightningcss

Unexpected token WhiteSpace(" ")
0004
Details

`0n+3` simplifies to just `3`. The zero-step term is redundant.

Source

a:nth-child(0n + 3) {
  color: red;
}

Expected

a:nth-child(3){color:red}

Outputs

clean-css

a:nth-child(0n+3){color:red}

csskit

a:nth-child(0n+ 3){color:red}

cssnano

a:nth-child(0n+3){color:red}

csso

a:nth-child(0n+3){color:red}

esbuild

a:nth-child(3){color:red}

lightningcss

a:nth-child(3){color:red}
0005
Details

`2n` is already the shortest form. Unlike `2n+1` -> `odd`, there is no shorter keyword for `2n` (even is same length). Minifiers should preserve `2n` as-is.

Source

a:nth-child(2n) {
  color: red;
}

Expected

a:nth-child(2n){color:red}

Outputs

clean-css

a:nth-child(2n){color:red}

csskit

a:nth-child(2n){color:red}

cssnano

a:nth-child(2n){color:red}

csso

a:nth-child(2n){color:red}

esbuild

a:nth-child(2n){color:red}

lightningcss

a:nth-child(2n){color:red}
0006
Details

`:nth-child(1)` is equivalent to `:first-child`, which is shorter.

Source

a:nth-child(1) {
  color: red;
}

Expected

a:first-child{color:red}

Outputs

clean-css

a:first-child{color:red}

csskit

a:nth-child(1){color:red}

cssnano

a:first-child{color:red}

csso

a:nth-child(1){color:red}

esbuild

a:nth-child(1){color:red}

lightningcss

a:first-child{color:red}
0007
Details

`:nth-last-child(1)` is equivalent to `:last-child`, which is shorter.

Source

a:nth-last-child(1) {
  color: red;
}

Expected

a:last-child{color:red}

Outputs

clean-css

a:last-child{color:red}

csskit

a:nth-last-child(1){color:red}

cssnano

a:last-child{color:red}

csso

a:nth-last-child(1){color:red}

esbuild

a:nth-last-child(1){color:red}

lightningcss

a:last-child{color:red}
0008
Details

`:nth-child(1n)` (equivalently `:nth-child(n)`) matches every child element, so the pseudo-class can be removed entirely. `a:nth-child(1n)` becomes just `a`.

Source

a:nth-child(1n) {
  color: red;
}

Expected

a{color:red}

Outputs

clean-css

a:nth-child(1n){color:red}

csskit

a:nth-child(1n){color:red}

cssnano

a:nth-child(1n){color:red}

csso

a:nth-child(n){color:red}

esbuild

a:nth-child(n){color:red}

lightningcss

a:nth-child(n){color:red}
0009
Details

`:nth-of-type(1)` is equivalent to `:first-of-type` and the latter is shorter.

Source

a:nth-of-type(1) {
  color: red;
}

Expected

a:first-of-type{color:red}

Outputs

clean-css

a:first-of-type{color:red}

csskit

a:nth-of-type(1){color:red}

cssnano

a:first-of-type{color:red}

csso

a:nth-of-type(1){color:red}

esbuild

a:nth-of-type(1){color:red}

lightningcss

a:first-of-type{color:red}
0010
Details

`:nth-last-of-type(1)` is equivalent to `:last-of-type` and the latter is shorter.

Source

a:nth-last-of-type(1) {
  color: red;
}

Expected

a:last-of-type{color:red}

Outputs

clean-css

a:last-of-type{color:red}

csskit

a:nth-last-of-type(1){color:red}

cssnano

a:last-of-type{color:red}

csso

a:nth-last-of-type(1){color:red}

esbuild

a:nth-last-of-type(1){color:red}

lightningcss

a:last-of-type{color:red}
0011
Details

`::before` can be shortened to `:before` for CSS2.1 pseudo-elements.

Source

a::before {
  content: "x";
}

Expected

a:before{content:"x"}

Outputs

clean-css

a::before{content:"x"}

csskit

a::before{content:"x"}

cssnano

a:before{content:"x"}

csso

a::before{content:"x"}

esbuild

a:before{content:"x"}

lightningcss

a:before{content:"x"}
0012
Details

The `*` selector must be preserved as-is, not removed or altered.

Source

* {
  margin: 0;
}

Expected

*{margin:0}

Outputs

clean-css

*{margin:0}

csskit

*{margin:0}

cssnano

*{margin:0}

csso

*{margin:0}

esbuild

*{margin:0}

lightningcss

*{margin:0}
0013
Details

`*#id` is equivalent to `#id`. The universal selector is implied.

Source

*#id {
  color: red;
}

Expected

#id{color:red}

Outputs

clean-css

#id{color:red}

csskit

*#id{color:red}

cssnano

#id{color:red}

csso

#id{color:red}

esbuild

*#id{color:red}

lightningcss

#id{color:red}
0014
Details

Quotes around attribute values can be removed when the value is a valid identifier.

Source

a[href="foo"] {
  color: red;
}

Expected

a[href=foo]{color:red}

Outputs

clean-css

a[href=foo]{color:red}

csskit

a[href="foo"]{color:red}

cssnano

a[href=foo]{color:red}

csso

a[href=foo]{color:red}

esbuild

a[href=foo]{color:red}

lightningcss

a[href=foo]{color:red}
0015
Details

`::view-transition-group()` is a modern pseudo-element and must NOT be shortened to a single colon. The `::` -> `:` shortening is only valid for CSS2.1 pseudo-elements (`::before`, `::after`, etc.).

Source

::view-transition-group(header) {
  animation-duration: 0.3s;
}

Expected

::view-transition-group(header){animation-duration:.3s}

Outputs

clean-css

::view-transition-group(header){animation-duration:.3s}

csskit

::view-transition-group(header){animation-duration:.3s}

cssnano

::view-transition-group(header){animation-duration:.3s}

csso

::view-transition-group(header){animation-duration:.3s}

esbuild

::view-transition-group(header){animation-duration:.3s}

lightningcss

::view-transition-group(header){animation-duration:.3s}
Subtotal 10 / 15 3 / 15 11 / 15 5 / 15 8 / 15 13 / 15

selectors-advanced

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`:is(a, b)` can be decomposed to `a,b` when all selectors are widely supported and share the same specificity. This is safe because `:is()` takes the highest specificity of its arguments, which equals each individual selector here.

Source

:is( a , b ) {
  color: red;
}

Expected

a,b{color:red}

Outputs

clean-css

:is( a ,b ){color:red}

csskit

:is(a,b){color:red}

cssnano

:is(a,b){color:red}

csso

:is(a,b){color:red}

esbuild

:is(a,b){color:red}

lightningcss

:is(a,b){color:red}
0002
Details

`:is(#a, .b)` cannot be decomposed to `#a,.b` because `:is()` takes the highest specificity of its arguments. Both `.b` and `#a` match with `(1,0,0)` inside `:is()`, but decomposed `.b` would only have `(0,1,0)`.

Source

:is(#a, .b) {
  color: red;
}

Expected

:is(#a,.b){color:red}

Outputs

clean-css

:is(#a,.b){color:red}

csskit

:is(#a,.b){color:red}

cssnano

:is(#a,.b){color:red}

csso

:is(#a,.b){color:red}

esbuild

:is(#a,.b){color:red}

lightningcss

:is(#a,.b){color:red}
0003
Details

Whitespace inside `:has()` around combinators can be removed, same as regular selector lists.

Source

a:has(> b) {
  color: red;
}

Expected

a:has(>b){color:red}

Outputs

clean-css

a:has(> b){color:red}

csskit

a:has(>b){color:red}

cssnano

a:has(>b){color:red}

csso

a:has(>b){color:red}

esbuild

a:has(>b){color:red}

lightningcss

a:has(>b){color:red}
0004
Details

Whitespace inside `:where()` around the selector list commas can be removed. `:where()` itself must not be replaced with `:is()` as they differ in specificity (`:where()` is zero-specificity).

Source

:where(.foo, .bar) {
  color: red;
}

Expected

:where(.foo,.bar){color:red}

Outputs

clean-css

:where(.foo,.bar){color:red}

csskit

:where(.foo,.bar){color:red}

cssnano

:where(.foo,.bar){color:red}

csso

:where(.foo,.bar){color:red}

esbuild

:where(.foo,.bar){color:red}

lightningcss

:where(.foo,.bar){color:red}
0005
Details

On form elements `:valid` and `:invalid` are a complete partition -- every input is one or the other. So `input:not(:invalid)` can be replaced with the shorter `input:valid`. This only holds when the type selector restricts to elements that participate in constraint validation.

Source

input:not(:invalid) {
  color: red;
}

Expected

input:valid{color:red}

Outputs

clean-css

input:not(:invalid){color:red}

csskit

input:not(:invalid){color:red}

cssnano

input:not(:invalid){color:red}

csso

input:not(:invalid){color:red}

esbuild

input:not(:invalid){color:red}

lightningcss

input:not(:invalid){color:red}
0006
Details

Every element has exactly one directionality -- `ltr` or `rtl`. The two values are a complete partition, so `:not(:dir(ltr))` is equivalent to `:dir(rtl)`.

Source

a:not(:dir(ltr)) {
  color: red;
}

Expected

a:dir(rtl){color:red}

Outputs

clean-css

a:not(:dir(ltr)){color:red}

csskit

a:not(:dir(ltr)){color:red}

cssnano

a:not(:dir(ltr)){color:red}

csso

a:not(:dir(ltr)){color:red}

esbuild

a:not(:dir(ltr)){color:red}

lightningcss

a:not(:dir(ltr)){color:red}
0007
Details

`:not(:not(X))` is logically equivalent to `X`. The double negation can be removed, producing a shorter selector.

Source

a:not(:not(.active)) {
  color: red;
}

Expected

a.active{color:red}

Outputs

clean-css

a:not(:not(.active)){color:red}

csskit

a:not(:not(.active)){color:red}

cssnano

a:not(:not(.active)){color:red}

csso

a:not(:not(.active)){color:red}

esbuild

a:not(:not(.active)){color:red}

lightningcss

a:not(:not(.active)){color:red}
0008
Details

On form-associated elements `:enabled` and `:disabled` are a complete partition. `input:not(:enabled)` can be replaced with the shorter `input:disabled`.

Source

input:not(:enabled) {
  color: red;
}

Expected

input:disabled{color:red}

Outputs

clean-css

input:not(:enabled){color:red}

csskit

input:not(:enabled){color:red}

cssnano

input:not(:enabled){color:red}

csso

input:not(:enabled){color:red}

esbuild

input:not(:enabled){color:red}

lightningcss

input:not(:enabled){color:red}
0009
Details

On form-associated elements `:required` and `:optional` are a complete partition. `input:not(:required)` can be replaced with `input:optional`.

Source

input:not(:required) {
  color: red;
}

Expected

input:optional{color:red}

Outputs

clean-css

input:not(:required){color:red}

csskit

input:not(:required){color:red}

cssnano

input:not(:required){color:red}

csso

input:not(:required){color:red}

esbuild

input:not(:required){color:red}

lightningcss

input:not(:required){color:red}
0010
Details

On hyperlink elements (`<a>`, `<area>`, `<link>` with href) `:link` and `:visited` are a complete partition. `a:not(:link)` can be replaced with the shorter `a:visited`.

Source

a:not(:link) {
  color: red;
}

Expected

a:visited{color:red}

Outputs

clean-css

a:not(:link){color:red}

csskit

a:not(:link){color:red}

cssnano

a:not(:link){color:red}

csso

a:not(:link){color:red}

esbuild

a:not(:link){color:red}

lightningcss

a:not(:link){color:red}
0011
Details

`:any-link` is equivalent to `:is(:link, :visited)` and is shorter. It matches any element that is a hyperlink source anchor (unvisited or visited).

Source

:is(:link, :visited) {
  color: red;
}

Expected

:any-link{color:red}

Outputs

clean-css

:is(:link,:visited){color:red}

csskit

:is(:link,:visited){color:red}

cssnano

:is(:link,:visited){color:red}

csso

:is(:link,:visited){color:red}

esbuild

:is(:link,:visited){color:red}

lightningcss

:is(:link,:visited){color:red}
0012
Details

When `:is()` restricts to form-associated elements, `:required` and `:optional` are a complete partition. `:is(textarea, input):not(:required)` can be replaced with `:is(textarea, input):optional`.

Source

:is(textarea, input):not(:required) {
  color: red;
}

Expected

:is(textarea,input):optional{color:red}

Outputs

clean-css

:is(textarea,input):not(:required){color:red}

csskit

:is(textarea,input):not(:required){color:red}

cssnano

:is(textarea,input):not(:required){color:red}

csso

:is(textarea,input):not(:required){color:red}

esbuild

:is(textarea,input):not(:required){color:red}

lightningcss

:is(textarea,input):not(:required){color:red}
0013
Details

`h1,h2,h3,h4,h5,h6` can be replaced with `:heading` per CSS Selectors Level 5. The `:heading` pseudo-class matches any element with a heading level. Its specificity is that of a class. No browser implements this yet.

Source

h1, h2, h3, h4, h5, h6 {
  color: red;
}

Expected

:heading{color:red}

Outputs

clean-css

h1,h2,h3,h4,h5,h6{color:red}

csskit

h1,h2,h3,h4,h5,h6{color:red}

cssnano

h1,h2,h3,h4,h5,h6{color:red}

csso

h1,h2,h3,h4,h5,h6{color:red}

esbuild

h1,h2,h3,h4,h5,h6{color:red}

lightningcss

h1,h2,h3,h4,h5,h6{color:red}
Subtotal 2 / 13 3 / 13 3 / 13 3 / 13 3 / 13 3 / 13

shorthands

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`margin: 10px 10px 10px 10px` collapses to `margin:10px` when all four values are identical.

Source

a {
  margin: 10px 10px 10px 10px;
}

Expected

a{margin:10px}

Outputs

clean-css

a{margin:10px}

csskit

a{margin:10px 10px 10px 10px}

cssnano

a{margin:10px}

csso

a{margin:10px}

esbuild

a{margin:10px}

lightningcss

a{margin:10px}
0002
Details

`margin: 10px 20px 10px 20px` collapses to `margin:10px 20px` when top/bottom and left/right pairs match.

Source

a {
  margin: 10px 20px 10px 20px;
}

Expected

a{margin:10px 20px}

Outputs

clean-css

a{margin:10px 20px}

csskit

a{margin:10px 20px 10px 20px}

cssnano

a{margin:10px 20px}

csso

a{margin:10px 20px}

esbuild

a{margin:10px 20px}

lightningcss

a{margin:10px 20px}
0003
Details

`margin: 10px 20px 30px 20px` collapses to `margin:10px 20px 30px` when left equals right.

Source

a {
  margin: 10px 20px 30px 20px;
}

Expected

a{margin:10px 20px 30px}

Outputs

clean-css

a{margin:10px 20px 30px}

csskit

a{margin:10px 20px 30px 20px}

cssnano

a{margin:10px 20px 30px}

csso

a{margin:10px 20px 30px}

esbuild

a{margin:10px 20px 30px}

lightningcss

a{margin:10px 20px 30px}
0004
Details

`background: none` and `background: 0 0` are equivalent. Both reset all background longhands to initial values -- `0 0` is parsed as `background-position` and all omitted longhands (including `background-image`) reset to their initial values. `0 0` is one byte shorter.

Source

a {
  background: none;
}

Expected

a{background:0 0}

Outputs

clean-css

a{background:0 0}

csskit

a{background:none}

cssnano

a{background:none}

csso

a{background:0 0}

esbuild

a{background:none}

lightningcss

a{background:0 0}
0005
Details

`border: 1px solid #000000` shortens the hex color to `#000` and removes the space before `#` since `#` unambiguously starts a hash token -- no whitespace is needed to separate it from the preceding keyword.

Source

a {
  border: 1px solid #000000;
}

Expected

a{border:1px solid#000}

Outputs

clean-css

a{border:1px solid #000}

csskit

a{border:1px solid#000}

cssnano

a{border:1px solid #000}

csso

a{border:1px solid #000}

esbuild

a{border:1px solid #000000}

lightningcss

a{border:1px solid #000}
0006
Details

`inset: 0px 0px 0px 0px` collapses to `inset:0`. Combines 4-to-1 shorthand collapse with zero-unit stripping. The `inset` property follows the same pattern as `margin`/`padding`.

Source

a {
  inset: 0px 0px 0px 0px;
}

Expected

a{inset:0}

Outputs

clean-css

a{inset:0px 0px 0px 0px}

csskit

a{inset:0 0 0 0}

cssnano

a{inset:0 0 0 0}

csso

a{inset:0 0 0 0}

esbuild

a{inset:0}

lightningcss

a{inset:0}
0007
Details

`padding: 10px 10px 10px 10px` collapses to `padding: 10px` when all four values are identical.

Source

a {
  padding: 10px 10px 10px 10px;
}

Expected

a{padding:10px}

Outputs

clean-css

a{padding:10px}

csskit

a{padding:10px 10px 10px 10px}

cssnano

a{padding:10px}

csso

a{padding:10px}

esbuild

a{padding:10px}

lightningcss

a{padding:10px}
0008
Details

`overflow-x` and `overflow-y` with the same value can be combined into `overflow` shorthand.

Source

a {
  overflow-x: hidden;
  overflow-y: hidden;
}

Expected

a{overflow:hidden}

Outputs

clean-css

a{overflow-x:hidden;overflow-y:hidden}

csskit

a{overflow-x:hidden;overflow-y:hidden}

cssnano

a{overflow-x:hidden;overflow-y:hidden}

csso

a{overflow-x:hidden;overflow-y:hidden}

esbuild

a{overflow-x:hidden;overflow-y:hidden}

lightningcss

a{overflow:hidden}
0009
Details

`row-gap` and `column-gap` with the same value can be combined into `gap` shorthand.

Source

a {
  row-gap: 10px;
  column-gap: 10px;
}

Expected

a{gap:10px}

Outputs

clean-css

a{row-gap:10px;column-gap:10px}

csskit

a{row-gap:10px;column-gap:10px}

cssnano

a{column-gap:10px;row-gap:10px}

csso

a{row-gap:10px;column-gap:10px}

esbuild

a{row-gap:10px;column-gap:10px}

lightningcss

a{gap:10px}
0010
Details

`flex-grow`, `flex-shrink`, and `flex-basis` can be combined into `flex` shorthand.

Source

a {
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: auto;
}

Expected

a{flex:1 0 auto}

Outputs

clean-css

a{flex-grow:1;flex-shrink:0;flex-basis:auto}

csskit

a{flex-grow:1;flex-shrink:0;flex-basis:auto}

cssnano

a{flex-basis:auto;flex-grow:1;flex-shrink:0}

csso

a{flex-grow:1;flex-shrink:0;flex-basis:auto}

esbuild

a{flex-grow:1;flex-shrink:0;flex-basis:auto}

lightningcss

a{flex:1 0 auto}
0011
Details

`outline-width`, `outline-style`, and `outline-color` can be combined into `outline` shorthand.

Source

a {
  outline-width: 1px;
  outline-style: solid;
  outline-color: red;
}

Expected

a{outline:1px solid red}

Outputs

clean-css

a{outline:red solid 1px}

csskit

a{outline-width:1px;outline-style:solid;outline-color:red}

cssnano

a{outline-color:red;outline-style:solid;outline-width:1px}

csso

a{outline-width:1px;outline-style:solid;outline-color:red}

esbuild

a{outline-width:1px;outline-style:solid;outline-color:red}

lightningcss

a{outline:1px solid red}
0012
Details

All four `border-*-radius` longhands with the same value collapse into `border-radius` shorthand.

Source

a {
  border-top-left-radius: 5px;
  border-top-right-radius: 5px;
  border-bottom-right-radius: 5px;
  border-bottom-left-radius: 5px;
}

Expected

a{border-radius:5px}

Outputs

clean-css

a{border-radius:5px}

csskit

a{border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-right-radius:5px;border-bottom-left-radius:5px}

cssnano

a{border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top-left-radius:5px;border-top-right-radius:5px}

csso

a{border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-right-radius:5px;border-bottom-left-radius:5px}

esbuild

a{border-radius:5px}

lightningcss

a{border-radius:5px}
0013
Details

Individual `margin-top/right/bottom/left` declarations collapse into `margin` shorthand with symmetric 2-value form.

Source

a {
  margin-top: 10px;
  margin-right: 20px;
  margin-bottom: 10px;
  margin-left: 20px;
}

Expected

a{margin:10px 20px}

Outputs

clean-css

a{margin:10px 20px}

csskit

a{margin-top:10px;margin-right:20px;margin-bottom:10px;margin-left:20px}

cssnano

a{margin:10px 20px}

csso

a{margin:10px 20px}

esbuild

a{margin:10px 20px}

lightningcss

a{margin:10px 20px}
0014
Details

`text-decoration-line/style/color` collapse into `text-decoration` shorthand, omitting `solid` (the default style).

Source

a {
  text-decoration-line: underline;
  text-decoration-style: solid;
  text-decoration-color: red;
}

Expected

a{text-decoration:underline red}

Outputs

clean-css

a{text-decoration-line:underline;text-decoration-style:solid;text-decoration-color:red}

csskit

a{text-decoration-line:underline;text-decoration-style:solid;text-decoration-color:red}

cssnano

a{text-decoration-color:red;text-decoration-line:underline;text-decoration-style:solid}

csso

a{text-decoration-line:underline;text-decoration-style:solid;text-decoration-color:red}

esbuild

a{text-decoration-line:underline;text-decoration-style:solid;text-decoration-color:red}

lightningcss

a{text-decoration-line:underline;text-decoration-style:solid;text-decoration-color:red}
0015
Details

Four padding longhand properties collapse to `padding` shorthand with value dedup.

Source

a {
  padding-top: 5px;
  padding-right: 10px;
  padding-bottom: 5px;
  padding-left: 10px;
}

Expected

a{padding:5px 10px}

Outputs

clean-css

a{padding:5px 10px}

csskit

a{padding-top:5px;padding-right:10px;padding-bottom:5px;padding-left:10px}

cssnano

a{padding:5px 10px}

csso

a{padding:5px 10px}

esbuild

a{padding:5px 10px}

lightningcss

a{padding:5px 10px}
0016
Details

All 12 border longhand properties collapse to a single `border` shorthand.

Source

a {
  border-top-width: 1px;
  border-right-width: 1px;
  border-bottom-width: 1px;
  border-left-width: 1px;
  border-top-style: solid;
  border-right-style: solid;
  border-bottom-style: solid;
  border-left-style: solid;
  border-top-color: red;
  border-right-color: red;
  border-bottom-color: red;
  border-left-color: red;
}

Expected

a{border:1px solid red}

Outputs

clean-css

a{border-width:1px;border-style:solid;border-top:1px solid red;border-right:1px solid red;border-bottom:1px solid red;border-color:red}

csskit

a{border-top-width:1px;border-right-width:1px;border-bottom-width:1px;border-left-width:1px;border-top-style:solid;border-right-style:solid;border-bottom-style:solid;border-left-style:solid;border-top-color:red;border-right-color:red;border-bottom-color:red;border-left-color:red}

cssnano

a{border:1px solid red}

csso

a{border-width:1px;border-style:solid;border-color:red}

esbuild

a{border-top-width:1px;border-right-width:1px;border-bottom-width:1px;border-left-width:1px;border-top-style:solid;border-right-style:solid;border-bottom-style:solid;border-left-style:solid;border-top-color:red;border-right-color:red;border-bottom-color:red;border-left-color:red}

lightningcss

a{border:1px solid red}
0017
Details

When top equals bottom and right equals left, padding collapses from 4 values to 2.

Source

a {
  padding: 5px 10px 5px 10px;
}

Expected

a{padding:5px 10px}

Outputs

clean-css

a{padding:5px 10px}

csskit

a{padding:5px 10px 5px 10px}

cssnano

a{padding:5px 10px}

csso

a{padding:5px 10px}

esbuild

a{padding:5px 10px}

lightningcss

a{padding:5px 10px}
0018
Details

When left equals right, the 4th value can be dropped. Top/bottom differ so 3 values remain.

Source

a {
  padding: 5px 10px 15px 10px;
}

Expected

a{padding:5px 10px 15px}

Outputs

clean-css

a{padding:5px 10px 15px}

csskit

a{padding:5px 10px 15px 10px}

cssnano

a{padding:5px 10px 15px}

csso

a{padding:5px 10px 15px}

esbuild

a{padding:5px 10px 15px}

lightningcss

a{padding:5px 10px 15px}
0019
Details

border-radius follows the same top-right-bottom-left collapsing rules as margin/padding.

Source

a {
  border-radius: 5px 10px 5px 10px;
}

Expected

a{border-radius:5px 10px}

Outputs

clean-css

a{border-radius:5px 10px}

csskit

a{border-radius:5px 10px 5px 10px}

cssnano

a{border-radius:5px 10px 5px 10px}

csso

a{border-radius:5px 10px 5px 10px}

esbuild

a{border-radius:5px 10px}

lightningcss

a{border-radius:5px 10px}
0020
Details

Both horizontal and vertical radii have identical values across all 4 corners, each side collapses to 1 value. The `/` separator must be preserved.

Source

a {
  border-radius: 10px 10px 10px 10px / 5px 5px 5px 5px;
}

Expected

a{border-radius:10px/5px}

Outputs

clean-css

a{border-radius:10px/5px}

csskit

a{border-radius:10px 10px 10px 10px / 5px 5px 5px 5px}

cssnano

a{border-radius:10px 10px 10px 10px/5px 5px 5px 5px}

csso

a{border-radius:10px 10px 10px 10px/5px 5px 5px 5px}

esbuild

a{border-radius:10px/5px}

lightningcss

a{border-radius:10px/5px}
0021
Details

The `inset` shorthand follows the same collapsing rules as margin/padding.

Source

a {
  inset: 10px 20px 10px 20px;
}

Expected

a{inset:10px 20px}

Outputs

clean-css

a{inset:10px 20px 10px 20px}

csskit

a{inset:10px 20px 10px 20px}

cssnano

a{inset:10px 20px 10px 20px}

csso

a{inset:10px 20px 10px 20px}

esbuild

a{inset:10px 20px}

lightningcss

a{inset:10px 20px}
0022
Details

When both values in a 2-value margin are identical, collapse to a single value.

Source

a {
  margin: 10px 10px;
}

Expected

a{margin:10px}

Outputs

clean-css

a{margin:10px}

csskit

a{margin:10px 10px}

cssnano

a{margin:10px}

csso

a{margin:10px}

esbuild

a{margin:10px}

lightningcss

a{margin:10px}
0023
Details

When align-items and justify-items are the same, they merge into a single-value `place-items`.

Source

a {
  align-items: center;
  justify-items: center;
}

Expected

a{place-items:center}

Outputs

clean-css

a{align-items:center;justify-items:center}

csskit

a{align-items:center;justify-items:center}

cssnano

a{align-items:center;justify-items:center}

csso

a{align-items:center;justify-items:center}

esbuild

a{align-items:center;justify-items:center}

lightningcss

a{place-items:center}
0024
Details

When align-content and justify-content are the same, they merge into a single-value `place-content`.

Source

a {
  align-content: center;
  justify-content: center;
}

Expected

a{place-content:center}

Outputs

clean-css

a{align-content:center;justify-content:center}

csskit

a{align-content:center;justify-content:center}

cssnano

a{align-content:center;justify-content:center}

csso

a{align-content:center;justify-content:center}

esbuild

a{align-content:center;justify-content:center}

lightningcss

a{place-content:center}
0025
Details

When align-self and justify-self are the same, they merge into a single-value `place-self`.

Source

a {
  align-self: center;
  justify-self: center;
}

Expected

a{place-self:center}

Outputs

clean-css

a{align-self:center;justify-self:center}

csskit

a{align-self:center;justify-self:center}

cssnano

a{align-self:center;justify-self:center}

csso

a{align-self:center;justify-self:center}

esbuild

a{align-self:center;justify-self:center}

lightningcss

a{place-self:center}
0026
Details

When row-gap and column-gap differ, they merge into a 2-value `gap` shorthand (row then column).

Source

a {
  column-gap: 10px;
  row-gap: 20px;
}

Expected

a{gap:20px 10px}

Outputs

clean-css

a{column-gap:10px;row-gap:20px}

csskit

a{column-gap:10px;row-gap:20px}

cssnano

a{column-gap:10px;row-gap:20px}

csso

a{column-gap:10px;row-gap:20px}

esbuild

a{column-gap:10px;row-gap:20px}

lightningcss

a{gap:20px 10px}
0027
Details

column-width and column-count merge into the `columns` shorthand.

Source

a {
  column-width: auto;
  column-count: 3;
}

Expected

a{columns:auto 3}

Outputs

clean-css

a{column-width:auto;column-count:3}

csskit

a{column-width:auto;column-count:3}

cssnano

a{columns:3}

csso

a{column-width:auto;column-count:3}

esbuild

a{column-width:auto;column-count:3}

lightningcss

a{column-width:auto;column-count:3}
0028
Details

list-style-type, list-style-position, and list-style-image merge into `list-style`. Default values (`disc` type, `none` image) can be omitted.

Source

a {
  list-style-type: disc;
  list-style-position: inside;
  list-style-image: none;
}

Expected

a{list-style:inside}

Outputs

clean-css

a{list-style:disc inside}

csskit

a{list-style-type:disc;list-style-position:inside;list-style-image:none}

cssnano

a{list-style-image:none;list-style-position:inside;list-style-type:disc}

csso

a{list-style-type:disc;list-style-position:inside;list-style-image:none}

esbuild

a{list-style-type:disc;list-style-position:inside;list-style-image:none}

lightningcss

a{list-style:inside}
0029
Details

Background longhands merge into `background` shorthand. All properties at their default values except background-color are omitted.

Source

a {
  background-color: red;
  background-image: none;
  background-repeat: repeat;
  background-position: 0% 0%;
  background-attachment: scroll;
}

Expected

a{background:red}

Outputs

clean-css

a{background-color:red;background-image:none;background-repeat:repeat;background-position:0 0;background-attachment:scroll}

csskit

a{background-color:red;background-image:none;background-repeat:repeat;background-position:0% 0%;background-attachment:scroll}

cssnano

a{background-attachment:scroll;background-color:red;background-image:none;background-position:0 0;background-repeat:repeat}

csso

a{background-color:red;background-image:none;background-repeat:repeat;background-position:0 0;background-attachment:scroll}

esbuild

a{background-color:red;background-image:none;background-repeat:repeat;background-position:0% 0%;background-attachment:scroll}

lightningcss

a{background-color:red;background-image:none;background-position:0 0;background-repeat:repeat;background-attachment:scroll}
0030
Details

font-style, font-weight, font-size, line-height, and font-family merge into `font` shorthand. `bold` optimizes to `700`.

Source

a {
  font-style: italic;
  font-weight: bold;
  font-size: 16px;
  line-height: 1.5;
  font-family: Arial, sans-serif;
}

Expected

a{font:italic 700 16px/1.5 Arial,sans-serif}

Outputs

clean-css

a{font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif}

csskit

a{font-style:italic;font-weight:bold;font-size:16px;line-height:1.5;font-family:Arial,sans-serif}

cssnano

a{font-family:Arial,sans-serif;font-size:16px;font-style:italic;font-weight:700;line-height:1.5}

csso

a{font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif}

esbuild

a{font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif}

lightningcss

a{font-family:Arial,sans-serif;font-size:16px;font-style:italic;font-weight:700;line-height:1.5}
0031
Details

transition-property, transition-duration, transition-timing-function, and transition-delay merge into `transition`. Default values (`ease` timing, `0s` delay) are omitted.

Source

a {
  transition-property: opacity;
  transition-duration: 0.3s;
  transition-timing-function: ease;
  transition-delay: 0s;
}

Expected

a{transition:opacity .3s}

Outputs

clean-css

a{transition:opacity .3s}

csskit

a{transition-property:opacity;transition-duration:.3s;transition-timing-function:ease;transition-delay:0}

cssnano

a{transition-delay:0s;transition-duration:.3s;transition-property:opacity;transition-timing-function:ease}

csso

a{transition-property:opacity;transition-duration:.3s;transition-timing-function:ease;transition-delay:0s}

esbuild

a{transition-property:opacity;transition-duration:.3s;transition-timing-function:ease;transition-delay:0s}

lightningcss

a{transition:opacity .3s}
0032
Details

All 8 animation longhands merge into `animation` shorthand. Default values (ease, 0s delay, 1 iteration, normal direction, none fill, running) are omitted.

Source

a {
  animation-name: slide;
  animation-duration: 1s;
  animation-timing-function: ease;
  animation-delay: 0s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: none;
  animation-play-state: running;
}

Expected

a{animation:slide 1s}

Outputs

clean-css

a{animation:1s slide}

csskit

a{animation-name:slide;animation-duration:1s;animation-timing-function:ease;animation-delay:0;animation-iteration-count:1;animation-direction:normal;animation-fill-mode:none;animation-play-state:running}

cssnano

a{animation-delay:0s;animation-direction:normal;animation-duration:1s;animation-fill-mode:none;animation-iteration-count:1;animation-name:slide;animation-play-state:running;animation-timing-function:ease}

csso

a{animation-name:slide;animation-duration:1s;animation-timing-function:ease;animation-delay:0s;animation-iteration-count:1;animation-direction:normal;animation-fill-mode:none;animation-play-state:running}

esbuild

a{animation-name:slide;animation-duration:1s;animation-timing-function:ease;animation-delay:0s;animation-iteration-count:1;animation-direction:normal;animation-fill-mode:none;animation-play-state:running}

lightningcss

a{animation-name:slide;animation-duration:1s;animation-timing-function:ease;animation-iteration-count:1;animation-direction:normal;animation-play-state:running;animation-delay:0s;animation-fill-mode:none}
0033
Details

`medium` is the default border-width value and can be omitted from the `border` shorthand.

Source

a {
  border: medium solid red;
}

Expected

a{border:solid red}

Outputs

clean-css

a{border:solid red}

csskit

a{border:medium solid red}

cssnano

a{border:solid red}

csso

a{border:medium solid red}

esbuild

a{border:medium solid red}

lightningcss

a{border:solid red}
0034
Details

Default background-position (0% 0%), background-repeat (repeat), and background-attachment (scroll) can be omitted from the `background` shorthand.

Source

a {
  background: red url(bg.png) 0% 0% repeat scroll;
}

Expected

a{background:red url(bg.png)}

Outputs

clean-css

a{background:url(bg.png) red}

csskit

a{background:red url(bg.png) 0% 0% repeat scroll}

cssnano

a{background:red url(bg.png) 0 0 repeat scroll}

csso

a{background:red url(bg.png)0 0}

esbuild

a{background:red url(bg.png) 0% 0% repeat scroll}

lightningcss

a{background:red url(bg.png)}
0035
Details

`normal` is the default for both font-style and font-weight; both can be omitted from the font shorthand.

Source

a {
  font: normal normal 16px/1.5 Arial, sans-serif;
}

Expected

a{font:16px/1.5 Arial,sans-serif}

Outputs

clean-css

a{font:16px/1.5 Arial,sans-serif}

csskit

a{font:normal normal 16px/1.5 Arial,sans-serif}

cssnano

a{font:normal normal 16px/1.5 Arial,sans-serif}

csso

a{font:16px/1.5 Arial,sans-serif}

esbuild

a{font: 16px/1.5 Arial,sans-serif}

lightningcss

a{font:16px/1.5 Arial,sans-serif}
0036
Details

Color values within shorthands like `outline` should still be optimized (#ff0000 -> red).

Source

a {
  outline: 1px solid #ff0000;
}

Expected

a{outline:1px solid red}

Outputs

clean-css

a{outline:#ff0000 solid 1px}

csskit

a{outline:1px solid red}

cssnano

a{outline:1px solid red}

csso

a{outline:1px solid red}

esbuild

a{outline:1px solid #ff0000}

lightningcss

a{outline:1px solid red}
0037
Details

`margin: 0 auto 0 auto` collapses to `margin: 0 auto`. The `auto` keyword participates in the same collapsing rules as length values.

Source

a {
  margin: 0 auto 0 auto;
}

Expected

a{margin:0 auto}

Outputs

clean-css

a{margin:0 auto}

csskit

a{margin:0 auto 0 auto}

cssnano

a{margin:0 auto}

csso

a{margin:0 auto}

esbuild

a{margin:0 auto}

lightningcss

a{margin:0 auto}
0038
Details

border-color follows the same top-right-bottom-left collapsing rules as margin/padding.

Source

a {
  border-color: red red red red;
}

Expected

a{border-color:red}

Outputs

clean-css

a{border-color:red}

csskit

a{border-color:red red red red}

cssnano

a{border-color:red}

csso

a{border-color:red}

esbuild

a{border-color:red red red red}

lightningcss

a{border-color:red}
0039
Details

When row-gap and column-gap are the same in a 2-value `gap`, collapse to a single value.

Source

a {
  gap: 10px 10px;
}

Expected

a{gap:10px}

Outputs

clean-css

a{gap:10px 10px}

csskit

a{gap:10px 10px}

cssnano

a{gap:10px 10px}

csso

a{gap:10px 10px}

esbuild

a{gap:10px 10px}

lightningcss

a{gap:10px}
0040
Details

When overflow-x and overflow-y are the same in a 2-value `overflow`, collapse to a single value.

Source

a {
  overflow: hidden hidden;
}

Expected

a{overflow:hidden}

Outputs

clean-css

a{overflow:hidden hidden}

csskit

a{overflow:hidden hidden}

cssnano

a{overflow:hidden hidden}

csso

a{overflow:hidden hidden}

esbuild

a{overflow:hidden hidden}

lightningcss

a{overflow:hidden}
0041
Details

border-image is declared before border longhands. A minifier collapsing the longhands to `border` must reorder it before `border-image`, since `border` resets border-image to its initial value.

Source

a {
  border-image: url(border.png) 30 round;
  border-width: 1px;
  border-style: solid;
  border-color: red;
}

Expected

a{border:1px solid red;border-image:url(border.png) 30 round}

Outputs

clean-css

a{border-image:url(border.png) 30 round;border:1px solid red}

csskit

a{border-image:url(border.png) 30 round;border-width:1px;border-style:solid;border-color:red}

cssnano

a{border:1px solid red;border-image:url(border.png) 30 round}

csso

a{border-image:url(border.png)30 round;border-width:1px;border-style:solid;border-color:red}

esbuild

a{border-image:url(border.png) 30 round;border-width:1px;border-style:solid;border-color:red}

lightningcss

a{border:1px solid red;border-image:url(border.png) 30 round}
0042
Details

Border longhands come before border-image. Collapsing longhands to `border` shorthand is safe since border-image already follows and overrides the reset.

Source

a {
  border-width: 1px;
  border-style: solid;
  border-color: red;
  border-image: url(border.png) 30 round;
}

Expected

a{border:1px solid red;border-image:url(border.png) 30 round}

Outputs

clean-css

a{border:1px solid red;border-image:url(border.png) 30 round}

csskit

a{border-width:1px;border-style:solid;border-color:red;border-image:url(border.png) 30 round}

cssnano

a{border:1px solid red;border-image:url(border.png) 30 round}

csso

a{border-width:1px;border-style:solid;border-color:red;border-image:url(border.png)30 round}

esbuild

a{border-width:1px;border-style:solid;border-color:red;border-image:url(border.png) 30 round}

lightningcss

a{border:1px solid red;border-image:url(border.png) 30 round}
0043
Details

Declaring border-image then `border` resets border-image to none. The border-image declaration is dead code and can be removed.

Source

a {
  border-image: url(border.png) 30 round;
  border: 1px solid red;
}

Expected

a{border:1px solid red}

Outputs

clean-css

a{border-image:url(border.png) 30 round;border:1px solid red}

csskit

a{border-image:url(border.png) 30 round;border:1px solid red}

cssnano

a{border:1px solid red;border-image:url(border.png) 30 round}

csso

a{border-image:url(border.png)30 round;border:1px solid red}

esbuild

a{border-image:url(border.png) 30 round;border:1px solid red}

lightningcss

a{border:1px solid red}
0044
Details

Both border and border-image expressed as longhands. Minifier should collapse each group into its respective shorthand, with `border` ordered before `border-image` to avoid the reset clobbering border-image.

Source

a {
  border-image-source: url(border.png);
  border-image-slice: 30;
  border-image-repeat: round;
  border-width: 4px;
  border-style: solid;
  border-color: transparent;
}

Expected

a{border:4px solid transparent;border-image:url(border.png) 30 round}

Outputs

clean-css

a{border-image-source:url(border.png);border-image-slice:30;border-image-repeat:round;border:4px solid transparent}

csskit

a{border-image-source:url(border.png);border-image-slice:30;border-image-repeat:round;border-width:4px;border-style:solid;border-color:transparent}

cssnano

a{border:4px solid #0000;border-image-repeat:round;border-image-slice:30;border-image-source:url(border.png)}

csso

a{border-image-source:url(border.png);border-image-slice:30;border-image-repeat:round;border-width:4px;border-style:solid;border-color:transparent}

esbuild

a{border-image-source:url(border.png);border-image-slice:30;border-image-repeat:round;border-width:4px;border-style:solid;border-color:transparent}

lightningcss

a{border:4px solid #0000;border-image-source:url(border.png);border-image-slice:30;border-image-repeat:round}
0045
Details

font-variant-ligatures is declared before font longhands. A minifier collapsing the longhands to `font` must reorder it before `font-variant-ligatures`, since `font` resets font-variant-ligatures to its initial value (`normal`).

Source

a {
  font-variant-ligatures: no-common-ligatures;
  font-style: italic;
  font-weight: bold;
  font-size: 16px;
  line-height: 1.5;
  font-family: Arial, sans-serif;
}

Expected

a{font:italic 700 16px/1.5 Arial,sans-serif;font-variant-ligatures:no-common-ligatures}

Validate

Outputs

clean-css

a{font-variant-ligatures:no-common-ligatures;font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif}

csskit

a{font-variant-ligatures:no-common-ligatures;font-style:italic;font-weight:bold;font-size:16px;line-height:1.5;font-family:Arial,sans-serif}

cssnano

a{font-family:Arial,sans-serif;font-size:16px;font-style:italic;font-variant-ligatures:no-common-ligatures;font-weight:700;line-height:1.5}

csso

a{font-variant-ligatures:no-common-ligatures;font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif}

esbuild

a{font-variant-ligatures:no-common-ligatures;font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif}

lightningcss

a{font-variant-ligatures:no-common-ligatures;font-family:Arial,sans-serif;font-size:16px;font-style:italic;font-weight:700;line-height:1.5}
0046
Details

Font longhands come before font-feature-settings. Collapsing longhands to `font` shorthand is safe since font-feature-settings already follows and overrides the implicit reset.

Source

a {
  font-style: italic;
  font-weight: bold;
  font-size: 16px;
  line-height: 1.5;
  font-family: Arial, sans-serif;
  font-feature-settings: "smcp" 1;
}

Expected

a{font:italic 700 16px/1.5 Arial,sans-serif;font-feature-settings:"smcp" 1}

Validate

Outputs

clean-css

a{font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif;font-feature-settings:"smcp" 1}

csskit

a{font-style:italic;font-weight:bold;font-size:16px;line-height:1.5;font-family:Arial,sans-serif;font-feature-settings:"smcp"1}

cssnano

a{font-family:Arial,sans-serif;font-feature-settings:"smcp" 1;font-size:16px;font-style:italic;font-weight:700;line-height:1.5}

csso

a{font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif;font-feature-settings:"smcp"1}

esbuild

a{font-style:italic;font-weight:700;font-size:16px;line-height:1.5;font-family:Arial,sans-serif;font-feature-settings:"smcp" 1}

lightningcss

a{font-feature-settings:"smcp" 1;font-family:Arial,sans-serif;font-size:16px;font-style:italic;font-weight:700;line-height:1.5}
0047
Details

Declaring font-variant-numeric then `font` resets font-variant-numeric to `normal`. The font-variant-numeric declaration is dead code and can be removed.

Source

a {
  font-variant-numeric: tabular-nums;
  font: italic 16px/1.5 Arial, sans-serif;
}

Expected

a{font:italic 16px/1.5 Arial,sans-serif}

Validate

Outputs

clean-css

a{font-variant-numeric:tabular-nums;font:italic 16px/1.5 Arial,sans-serif}

csskit

a{font-variant-numeric:tabular-nums;font:italic 16px/1.5 Arial,sans-serif}

cssnano

a{font:italic 16px/1.5 Arial,sans-serif;font-variant-numeric:tabular-nums}

csso

a{font-variant-numeric:tabular-nums;font:italic 16px/1.5 Arial,sans-serif}

esbuild

a{font-variant-numeric:tabular-nums;font:italic 16px/1.5 Arial,sans-serif}

lightningcss

a{font-variant-numeric:tabular-nums;font:italic 16px/1.5 Arial,sans-serif}
0048
Details

Font longhands and font-kerning are both expressed as longhands. Minifier should collapse the font longhands into `font` shorthand, keeping `font` before `font-kerning` since `font` resets font-kerning to `auto`.

Source

a {
  font-style: normal;
  font-weight: 400;
  font-size: 14px;
  line-height: 1.4;
  font-family: Georgia, serif;
  font-kerning: none;
}

Expected

a{font:14px/1.4 Georgia,serif;font-kerning:none}

Validate

Outputs

clean-css

a{font-style:normal;font-weight:400;font-size:14px;line-height:1.4;font-family:Georgia,serif;font-kerning:none}

csskit

a{font-style:normal;font-weight:400;font-size:14px;line-height:1.4;font-family:Georgia,serif;font-kerning:none}

cssnano

a{font-family:Georgia,serif;font-kerning:none;font-size:14px;font-style:normal;font-weight:400;line-height:1.4}

csso

a{font-style:normal;font-weight:400;font-size:14px;line-height:1.4;font-family:Georgia,serif;font-kerning:none}

esbuild

a{font-style:normal;font-weight:400;font-size:14px;line-height:1.4;font-family:Georgia,serif;font-kerning:none}

lightningcss

a{font-kerning:none;font-family:Georgia,serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.4}
0049
Details

mask-border is declared before mask longhands. A minifier collapsing the longhands to `mask` must reorder it before `mask-border`, since `mask` resets mask-border to its initial value.

Source

a {
  mask-border: url(mask.png) 25 / 10px round;
  mask-image: linear-gradient(black, transparent);
  mask-size: cover;
  mask-repeat: no-repeat;
}

Expected

a{mask:linear-gradient(#000,transparent) no-repeat/cover;mask-border:url(mask.png) 25/10px round}

Outputs

clean-css

a{mask-border:url(mask.png) 25/10px round;mask-image:linear-gradient(black,transparent);mask-size:cover;mask-repeat:no-repeat}

csskit

a{mask-border:url(mask.png) 25 / 10px round;mask-image:linear-gradient(black,transparent);mask-size:cover;mask-repeat:no-repeat}

cssnano

a{mask-border:url(mask.png) 25/10px round;mask-image:linear-gradient(#000,#0000);mask-repeat:no-repeat;mask-size:cover}

csso

a{mask-border:url(mask.png)25/10px round;mask-image:linear-gradient(#000,transparent);mask-size:cover;mask-repeat:no-repeat}

esbuild

a{mask-border:url(mask.png) 25 / 10px round;mask-image:linear-gradient(black,transparent);mask-size:cover;mask-repeat:no-repeat}

lightningcss

a{mask-image:linear-gradient(#000,#0000);mask-size:cover;mask-repeat:no-repeat;mask-border:url(mask.png) 25/10px round}
0050
Details

Mask longhands come before mask-border. Collapsing longhands to `mask` shorthand is safe since mask-border already follows and overrides the implicit reset.

Source

a {
  mask-image: linear-gradient(black, transparent);
  mask-repeat: no-repeat;
  mask-border: url(mask.png) 25 round;
}

Expected

a{mask:linear-gradient(#000,transparent) no-repeat;mask-border:url(mask.png) 25 round}

Outputs

clean-css

a{mask-image:linear-gradient(black,transparent);mask-repeat:no-repeat;mask-border:url(mask.png) 25 round}

csskit

a{mask-image:linear-gradient(black,transparent);mask-repeat:no-repeat;mask-border:url(mask.png) 25 round}

cssnano

a{mask-border:url(mask.png) 25 round;mask-image:linear-gradient(#000,#0000);mask-repeat:no-repeat}

csso

a{mask-image:linear-gradient(#000,transparent);mask-repeat:no-repeat;mask-border:url(mask.png)25 round}

esbuild

a{mask-image:linear-gradient(black,transparent);mask-repeat:no-repeat;mask-border:url(mask.png) 25 round}

lightningcss

a{mask-image:linear-gradient(#000,#0000);mask-repeat:no-repeat;mask-border:url(mask.png) 25 round}
0051
Details

Declaring mask-border then `mask` resets mask-border to its initial value. The mask-border declaration is dead code and can be removed.

Source

a {
  mask-border: url(mask.png) 25 round;
  mask: linear-gradient(black, transparent);
}

Expected

a{mask:linear-gradient(#000,transparent)}

Outputs

clean-css

a{mask-border:url(mask.png) 25 round;mask:linear-gradient(black,transparent)}

csskit

a{mask-border:url(mask.png) 25 round;mask:linear-gradient(black,transparent)}

cssnano

a{mask:linear-gradient(#000,#0000);mask-border:url(mask.png) 25 round}

csso

a{mask-border:url(mask.png)25 round;mask:linear-gradient(#000,transparent)}

esbuild

a{mask-border:url(mask.png) 25 round;mask:linear-gradient(black,transparent)}

lightningcss

a{mask:linear-gradient(#000,#0000);mask-border:url(mask.png) 25 round}
0052
Details

font-variation-settings is declared before font longhands. A minifier collapsing the longhands to `font` must reorder it before `font-variation-settings`, since `font` resets font-variation-settings to its initial value (`normal`).

Source

a {
  font-variation-settings: "wght" 600, "wdth" 75;
  font-style: normal;
  font-weight: 400;
  font-size: 18px;
  line-height: 1.6;
  font-family: Inter, sans-serif;
}

Expected

a{font:18px/1.6 Inter,sans-serif;font-variation-settings:"wght" 600,"wdth" 75}

Validate

Outputs

clean-css

a{font-variation-settings:"wght" 600,"wdth" 75;font-style:normal;font-weight:400;font-size:18px;line-height:1.6;font-family:Inter,sans-serif}

csskit

a{font-variation-settings:"wght"600,"wdth"75;font-style:normal;font-weight:400;font-size:18px;line-height:1.6;font-family:Inter,sans-serif}

cssnano

a{font-family:Inter,sans-serif;font-size:18px;font-style:normal;font-variation-settings:"wght" 600,"wdth" 75;font-weight:400;line-height:1.6}

csso

a{font-variation-settings:"wght"600,"wdth"75;font-style:normal;font-weight:400;font-size:18px;line-height:1.6;font-family:Inter,sans-serif}

esbuild

a{font-variation-settings:"wght" 600,"wdth" 75;font-style:normal;font-weight:400;font-size:18px;line-height:1.6;font-family:Inter,sans-serif}

lightningcss

a{font-variation-settings:"wght" 600, "wdth" 75;font-family:Inter,sans-serif;font-size:18px;font-style:normal;font-weight:400;line-height:1.6}
0053
Details

Merging padding-top/right/bottom/left into `padding` shorthand is unsafe when values use `var()`. If any variable is undefined or empty, shorthand fallback behavior differs from individual longhands.

Source

a {
  padding-top: var(--pt);
  padding-right: var(--pr);
  padding-bottom: var(--pb);
  padding-left: var(--pl);
}

Expected

a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

Outputs

clean-css

a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

csskit

a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

cssnano

a{padding:var(--pt) var(--pr) var(--pb) var(--pl)}

csso

a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

esbuild

a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

lightningcss

a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}
0054
Details

`font-weight: bold` followed by `font: 14px serif` is dead code because the `font` shorthand resets `font-weight` to `normal`. The longhand can be removed.

Source

a {
  font-weight: bold;
  font: 14px serif;
}

Expected

a{font:14px serif}

Outputs

clean-css

a{font:14px serif}

csskit

a{font-weight:bold;font:14px serif}

cssnano

a{font-weight:700;font:14px serif}

csso

a{font:14px serif}

esbuild

a{font-weight:700;font:14px serif}

lightningcss

a{font:14px serif}
0055
Details

Merging `border-width`, `border-style`, `border-color` into `border` shorthand is unsafe when values use `var()`. The variables may expand to multi-value strings (e.g. `--border-width: 0 0 0 1px`) which are valid in longhands but not in the shorthand form.

Source

a {
  border-width: var(--border-width);
  border-style: var(--border-style);
  border-color: var(--border-color);
}

Expected

a{border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color)}

Outputs

clean-css

a{border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color)}

csskit

a{border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color)}

cssnano

a{border-color:var(--border-color);border-style:var(--border-style);border-width:var(--border-width)}

csso

a{border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color)}

esbuild

a{border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color)}

lightningcss

a{border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color)}
0056
Details

When one longhand has `!important`, the longhands can still be partially merged into a shorthand followed by the important longhand override. The `!important` declaration wins over the shorthand's reset of the same property.

Source

a {
  margin-top: 10px !important;
  margin-right: 20px;
  margin-bottom: 10px;
  margin-left: 20px;
}

Expected

a{margin:10px 20px;margin-top:10px!important}

Outputs

clean-css

a{margin-top:10px!important;margin-right:20px;margin-bottom:10px;margin-left:20px}

csskit

a{margin-top:10px!important;margin-right:20px;margin-bottom:10px;margin-left:20px}

cssnano

a{margin-bottom:10px;margin-left:20px;margin-right:20px;margin-top:10px!important}

csso

a{margin-top:10px!important;margin-right:20px;margin-bottom:10px;margin-left:20px}

esbuild

a{margin-top:10px!important;margin-right:20px;margin-bottom:10px;margin-left:20px}

lightningcss

a{margin-bottom:10px;margin-left:20px;margin-right:20px;margin-top:10px!important}
0057
Details

A valid fallback does not make merging safe. The fallback only triggers when the property is undefined. If the property is defined with an invalid value (e.g. `--pt: banana`), the fallback is ignored and the entire shorthand declaration fails at computed value time.

Source

a {
  padding-top: var(--pt, 10px);
  padding-right: var(--pr, 20px);
  padding-bottom: var(--pb, 10px);
  padding-left: var(--pl, 20px);
}

Expected

a{padding-top:var(--pt,10px);padding-right:var(--pr,20px);padding-bottom:var(--pb,10px);padding-left:var(--pl,20px)}

Outputs

clean-css

a{padding-top:var(--pt,10px);padding-right:var(--pr,20px);padding-bottom:var(--pb,10px);padding-left:var(--pl,20px)}

csskit

a{padding-top:var(--pt,10px);padding-right:var(--pr,20px);padding-bottom:var(--pb,10px);padding-left:var(--pl,20px)}

cssnano

a{padding:var(--pt,10px) var(--pr,20px) var(--pb,10px) var(--pl,20px)}

csso

a{padding-top:var(--pt, 10px);padding-right:var(--pr, 20px);padding-bottom:var(--pb, 10px);padding-left:var(--pl, 20px)}

esbuild

a{padding-top:var(--pt, 10px);padding-right:var(--pr, 20px);padding-bottom:var(--pb, 10px);padding-left:var(--pl, 20px)}

lightningcss

a{padding-top:var(--pt,10px);padding-right:var(--pr,20px);padding-bottom:var(--pb,10px);padding-left:var(--pl,20px)}
0058
Details

When `var()` fallbacks are themselves `var()` references, the final value cannot be statically determined. Merging into shorthand is unsafe because the nested variable may resolve to an invalid or multi-value result.

Source

a {
  padding-top: var(--pt, var(--fallback));
  padding-right: var(--pr, var(--fallback));
  padding-bottom: var(--pb, var(--fallback));
  padding-left: var(--pl, var(--fallback));
}

Expected

a{padding-top:var(--pt,var(--fallback));padding-right:var(--pr,var(--fallback));padding-bottom:var(--pb,var(--fallback));padding-left:var(--pl,var(--fallback))}

Outputs

clean-css

a{padding-top:var(--pt,var(--fallback));padding-right:var(--pr,var(--fallback));padding-bottom:var(--pb,var(--fallback));padding-left:var(--pl,var(--fallback))}

csskit

a{padding-top:var(--pt,var(--fallback));padding-right:var(--pr,var(--fallback));padding-bottom:var(--pb,var(--fallback));padding-left:var(--pl,var(--fallback))}

cssnano

a{padding:var(--pt,var(--fallback)) var(--pr,var(--fallback)) var(--pb,var(--fallback)) var(--pl,var(--fallback))}

csso

a{padding-top:var(--pt, var(--fallback));padding-right:var(--pr, var(--fallback));padding-bottom:var(--pb, var(--fallback));padding-left:var(--pl, var(--fallback))}

esbuild

a{padding-top:var(--pt, var(--fallback));padding-right:var(--pr, var(--fallback));padding-bottom:var(--pb, var(--fallback));padding-left:var(--pl, var(--fallback))}

lightningcss

a{padding-top:var(--pt,var(--fallback));padding-right:var(--pr,var(--fallback));padding-bottom:var(--pb,var(--fallback));padding-left:var(--pl,var(--fallback))}
0059
Details

A valid fallback does not make merging safe. The fallback only triggers when the property is undefined. If the property is defined with an invalid value, the fallback is ignored and the entire shorthand declaration fails at computed value time.

Source

a {
  border-width: var(--bw, 1px);
  border-style: var(--bs, solid);
  border-color: var(--bc, red);
}

Expected

a{border-width:var(--bw,1px);border-style:var(--bs,solid);border-color:var(--bc,red)}

Outputs

clean-css

a{border-width:var(--bw,1px);border-style:var(--bs,solid);border-color:var(--bc,red)}

csskit

a{border-width:var(--bw,1px);border-style:var(--bs,solid);border-color:var(--bc,red)}

cssnano

a{border-color:var(--bc,red);border-style:var(--bs,solid);border-width:var(--bw,1px)}

csso

a{border-width:var(--bw, 1px);border-style:var(--bs, solid);border-color:var(--bc, red)}

esbuild

a{border-width:var(--bw, 1px);border-style:var(--bs, solid);border-color:var(--bc, red)}

lightningcss

a{border-width:var(--bw,1px);border-style:var(--bs,solid);border-color:var(--bc,red)}
0060
Details

If even one `var()` has an unresolvable fallback (nested `var()`, no fallback, etc.), the longhands cannot be safely merged into `border` shorthand because the runtime value may expand to something invalid in shorthand context.

Source

a {
  border-width: var(--bw, var(--fallback));
  border-style: var(--bs, solid);
  border-color: var(--bc, red);
}

Expected

a{border-width:var(--bw,var(--fallback));border-style:var(--bs,solid);border-color:var(--bc,red)}

Outputs

clean-css

a{border-width:var(--bw,var(--fallback));border-style:var(--bs,solid);border-color:var(--bc,red)}

csskit

a{border-width:var(--bw,var(--fallback));border-style:var(--bs,solid);border-color:var(--bc,red)}

cssnano

a{border-color:var(--bc,red);border-style:var(--bs,solid);border-width:var(--bw,var(--fallback))}

csso

a{border-width:var(--bw, var(--fallback));border-style:var(--bs, solid);border-color:var(--bc, red)}

esbuild

a{border-width:var(--bw, var(--fallback));border-style:var(--bs, solid);border-color:var(--bc, red)}

lightningcss

a{border-width:var(--bw,var(--fallback));border-style:var(--bs,solid);border-color:var(--bc,red)}
0061
Details

When each custom property has an `@property` rule constraining its syntax to `<length>`, the value is guaranteed valid at parse time. Invalid values are rejected and fall back to `initial-value`, so shorthand merge is safe.

Source

@property --pt {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@property --pr {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@property --pb {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@property --pl {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
a {
  padding-top: var(--pt);
  padding-right: var(--pr);
  padding-bottom: var(--pb);
  padding-left: var(--pl);
}

Expected

@property --pt{syntax:"<length>";inherits:false;initial-value:0px}@property --pr{syntax:"<length>";inherits:false;initial-value:0px}@property --pb{syntax:"<length>";inherits:false;initial-value:0px}@property --pl{syntax:"<length>";inherits:false;initial-value:0px}a{padding:var(--pt) var(--pr) var(--pb) var(--pl)}

Outputs

clean-css

@property --pt{syntax:"<length>";inherits:false;initial-value:0px}@property --pr{syntax:"<length>";inherits:false;initial-value:0px}@property --pb{syntax:"<length>";inherits:false;initial-value:0px}@property --pl{syntax:"<length>";inherits:false;initial-value:0px}a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

csskit

@property --pt{syntax:"<length>";inherits:false;initial-value:0px}@property --pr{syntax:"<length>";inherits:false;initial-value:0px}@property --pb{syntax:"<length>";inherits:false;initial-value:0px}@property --pl{syntax:"<length>";inherits:false;initial-value:0px}a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

cssnano

@property --pt{syntax:"<length>";inherits:false;initial-value:0}@property --pr{syntax:"<length>";inherits:false;initial-value:0}@property --pb{syntax:"<length>";inherits:false;initial-value:0}@property --pl{syntax:"<length>";inherits:false;initial-value:0}a{padding:var(--pt) var(--pr) var(--pb) var(--pl)}

csso

@property --pt{syntax:"<length>";inherits:false;initial-value:0}@property --pr{syntax:"<length>";inherits:false;initial-value:0}@property --pb{syntax:"<length>";inherits:false;initial-value:0}@property --pl{syntax:"<length>";inherits:false;initial-value:0}a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

esbuild

@property --pt{syntax: "<length>"; inherits: false; initial-value: 0px;}@property --pr{syntax: "<length>"; inherits: false; initial-value: 0px;}@property --pb{syntax: "<length>"; inherits: false; initial-value: 0px;}@property --pl{syntax: "<length>"; inherits: false; initial-value: 0px;}a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}

lightningcss

@property --pt{syntax:"<length>";inherits:false;initial-value:0}@property --pr{syntax:"<length>";inherits:false;initial-value:0}@property --pb{syntax:"<length>";inherits:false;initial-value:0}@property --pl{syntax:"<length>";inherits:false;initial-value:0}a{padding-top:var(--pt);padding-right:var(--pr);padding-bottom:var(--pb);padding-left:var(--pl)}
0062
Details

When each custom property has an `@property` rule constraining its syntax, invalid values are rejected at parse time and fall back to `initial-value`. The variable is guaranteed to produce a single valid component, so shorthand merge is safe.

Source

@property --bw {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@property --bs {
  syntax: "<custom-ident>";
  inherits: false;
  initial-value: none;
}
@property --bc {
  syntax: "<color>";
  inherits: false;
  initial-value: black;
}
a {
  border-width: var(--bw);
  border-style: var(--bs);
  border-color: var(--bc);
}

Expected

@property --bw{syntax:"<length>";inherits:false;initial-value:0px}@property --bs{syntax:"<custom-ident>";inherits:false;initial-value:none}@property --bc{syntax:"<color>";inherits:false;initial-value:#000}a{border:var(--bw) var(--bs) var(--bc)}

Outputs

clean-css

@property --bw{syntax:"<length>";inherits:false;initial-value:0px}@property --bs{syntax:"<custom-ident>";inherits:false;initial-value:none}@property --bc{syntax:"<color>";inherits:false;initial-value:black}a{border-width:var(--bw);border-style:var(--bs);border-color:var(--bc)}

csskit

@property --bw{syntax:"<length>";inherits:false;initial-value:0px}@property --bs{syntax:"<custom-ident>";inherits:false;initial-value:none}@property --bc{syntax:"<color>";inherits:false;initial-value:black}a{border-width:var(--bw);border-style:var(--bs);border-color:var(--bc)}

cssnano

@property --bw{syntax:"<length>";inherits:false;initial-value:0}@property --bs{syntax:"<custom-ident>";inherits:false;initial-value:none}@property --bc{syntax:"<color>";inherits:false;initial-value:#000}a{border-color:var(--bc);border-style:var(--bs);border-width:var(--bw)}

csso

@property --bw{syntax:"<length>";inherits:false;initial-value:0}@property --bs{syntax:"<custom-ident>";inherits:false;initial-value:none}@property --bc{syntax:"<color>";inherits:false;initial-value:black}a{border-width:var(--bw);border-style:var(--bs);border-color:var(--bc)}

esbuild

@property --bw{syntax: "<length>"; inherits: false; initial-value: 0px;}@property --bs{syntax: "<custom-ident>"; inherits: false; initial-value: none;}@property --bc{syntax: "<color>"; inherits: false; initial-value: black;}a{border-width:var(--bw);border-style:var(--bs);border-color:var(--bc)}

lightningcss

@property --bw{syntax:"<length>";inherits:false;initial-value:0}@property --bs{syntax:"<custom-ident>";inherits:false;initial-value:none}@property --bc{syntax:"<color>";inherits:false;initial-value:#000}a{border-width:var(--bw);border-style:var(--bs);border-color:var(--bc)}
0063
Details

`top: 0; right: 0; bottom: 0; left: 0` merges into `inset: 0`. The `inset` shorthand sets all four physical inset properties and is shorter when all values are equal.

Source

a {
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

Expected

a{inset:0}

Outputs

clean-css

a{top:0;right:0;bottom:0;left:0}

csskit

a{top:0;right:0;bottom:0;left:0}

cssnano

a{bottom:0;left:0;right:0;top:0}

csso

a{top:0;right:0;bottom:0;left:0}

esbuild

a{inset:0}

lightningcss

a{inset:0}
0064
Details

`margin-inline-start: 10px; margin-inline-end: 10px` merges into `margin-inline: 10px`. When both logical sides are equal, the two-value form collapses to a single value.

Source

a {
  margin-inline-start: 10px;
  margin-inline-end: 10px;
}

Expected

a{margin-inline:10px}

Outputs

clean-css

a{margin-inline-start:10px;margin-inline-end:10px}

csskit

a{margin-inline-start:10px;margin-inline-end:10px}

cssnano

a{margin-inline-end:10px;margin-inline-start:10px}

csso

a{margin-inline-start:10px;margin-inline-end:10px}

esbuild

a{margin-inline-start:10px;margin-inline-end:10px}

lightningcss

a{margin-inline:10px}
0065
Details

`position-try-order: normal; position-try-fallbacks: flip-block, --custom` merges into `position-try: flip-block, --custom`. The `normal` order is the initial value and can be omitted in the shorthand.

Source

a {
  position-try-order: normal;
  position-try-fallbacks: flip-block, --custom;
}

Expected

a{position-try:flip-block,--custom}

Outputs

clean-css

a{position-try-order:normal;position-try-fallbacks:flip-block,--custom}

csskit

a{position-try-order:normal;position-try-fallbacks:flip-block,--custom}

cssnano

a{position-try-fallbacks:flip-block,--custom;position-try-order:normal}

csso

a{position-try-order:normal;position-try-fallbacks:flip-block,--custom}

esbuild

a{position-try-order:normal;position-try-fallbacks:flip-block,--custom}

lightningcss

a{position-try-order:normal;position-try-fallbacks:flip-block, --custom}
0066
Details

`border-top: 1px solid blue` followed by `border: 2px solid red` is dead code because the `border` shorthand resets all sides including `border-top`.

Source

a {
  border-top: 1px solid blue;
  border: 2px solid red;
}

Expected

a{border:2px solid red}

Outputs

clean-css

a{border-top:1px solid #00f;border:2px solid red}

csskit

a{border-top:1px solid blue;border:2px solid red}

cssnano

a{border:2px solid red}

csso

a{border:2px solid red}

esbuild

a{border-top:1px solid blue;border:2px solid red}

lightningcss

a{border:2px solid red}
0067
Details

`border-block-start: 1px solid blue` followed by `border-block: 2px solid red` is dead code because `border-block` resets both `border-block-start` and `border-block-end`.

Source

a {
  border-block-start: 1px solid blue;
  border-block: 2px solid red;
}

Expected

a{border-block:2px solid red}

Outputs

clean-css

a{border-block-start:1px solid blue;border-block:2px solid red}

csskit

a{border-block-start:1px solid blue;border-block:2px solid red}

cssnano

a{border-block-start:1px solid blue;border-block:2px solid red}

csso

a{border-block-start:1px solid #00f;border-block:2px solid red}

esbuild

a{border-block-start:1px solid blue;border-block:2px solid red}

lightningcss

a{border-block:2px solid red}
0068
Details

`padding-inline-start: 10px` followed by `padding-inline: 20px` is dead code because `padding-inline` resets both `padding-inline-start` and `padding-inline-end`.

Source

a {
  padding-inline-start: 10px;
  padding-inline: 20px;
}

Expected

a{padding-inline:20px}

Outputs

clean-css

a{padding-inline-start:10px;padding-inline:20px}

csskit

a{padding-inline-start:10px;padding-inline:20px}

cssnano

a{padding-inline-start:10px;padding-inline:20px}

csso

a{padding-inline-start:10px;padding-inline:20px}

esbuild

a{padding-inline-start:10px;padding-inline:20px}

lightningcss

a{padding-inline:20px}
0069
Details

`margin-top: 10px` followed by `margin: 20px` is dead code because the `margin` shorthand resets all four physical sides.

Source

a {
  margin-top: 10px;
  margin: 20px;
}

Expected

a{margin:20px}

Outputs

clean-css

a{margin:20px}

csskit

a{margin-top:10px;margin:20px}

cssnano

a{margin:20px}

csso

a{margin:20px}

esbuild

a{margin:20px}

lightningcss

a{margin:20px}
0070
Details

`border-top-width: 1px` followed by `border-width: 2px` is dead code because `border-width` resets all four physical border widths.

Source

a {
  border-top-width: 1px;
  border-width: 2px;
}

Expected

a{border-width:2px}

Outputs

clean-css

a{border-width:2px}

csskit

a{border-top-width:1px;border-width:2px}

cssnano

a{border-width:2px}

csso

a{border-width:2px}

esbuild

a{border-top-width:1px;border-width:2px}

lightningcss

a{border-width:2px}
0071
Details

`border-top-color: blue` followed by `border-top: 2px solid red` is dead code because `border-top` resets `border-top-color`, `border-top-style`, and `border-top-width`.

Source

a {
  border-top-color: blue;
  border-top: 2px solid red;
}

Expected

a{border-top:2px solid red}

Outputs

clean-css

a{border-top:2px solid red}

csskit

a{border-top-color:blue;border-top:2px solid red}

cssnano

a{border-top:2px solid red}

csso

a{border-top:2px solid red}

esbuild

a{border-top-color:#00f;border-top:2px solid red}

lightningcss

a{border-top:2px solid red}
0072
Details

`inset-block-start: 10px` followed by `inset-block: 20px` is dead code because `inset-block` resets both `inset-block-start` and `inset-block-end`.

Source

a {
  inset-block-start: 10px;
  inset-block: 20px;
}

Expected

a{inset-block:20px}

Outputs

clean-css

a{inset-block-start:10px;inset-block:20px}

csskit

a{inset-block-start:10px;inset-block:20px}

cssnano

a{inset-block-start:10px;inset-block:20px}

csso

a{inset-block-start:10px;inset-block:20px}

esbuild

a{inset-block-start:10px;inset-block:20px}

lightningcss

a{inset-block:20px}
Subtotal 29 / 72 8 / 72 20 / 72 21 / 72 19 / 72 53 / 72

starting-style

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Basic whitespace removal in `@starting-style`. Defines the initial style for CSS transitions when an element is first displayed.

Source

@starting-style {
  .card {
    opacity: 0;
  }
}

Expected

@starting-style{.card{opacity:0}}

Outputs

clean-css

csskit

@starting-style{.card{opacity:0}}

cssnano

@starting-style{.card{opacity:0}}

csso

@starting-style{.card{opacity:0}}

esbuild

@starting-style{.card{opacity:0}}

lightningcss

@starting-style{.card{opacity:0}}
0002
Details

`@starting-style` with `display: none` and `opacity: 0` defines the entry state for a discrete transition. A minifier must not remove this block even though the values appear to match the element's hidden state -- without it the entry animation does not play.

Source

.card {
  transition: opacity 0.5s, display 0.5s allow-discrete;
}

@starting-style {
  .card {
    opacity: 0;
    display: none;
  }
}

Expected

.card{transition:opacity .5s,display .5s allow-discrete}@starting-style{.card{opacity:0;display:none}}

Outputs

clean-css

@starting-style{display:none}

csskit

.card{transition:opacity .5s,display .5s allow-discrete}@starting-style{.card{opacity:0;display:none}}

cssnano

.card{transition:opacity .5s,display allow-discrete .5s}@starting-style{.card{display:none;opacity:0}}

csso

.card{transition:opacity .5s,display .5s allow-discrete}@starting-style{.card{opacity:0;display:none}}

esbuild

.card{transition:opacity .5s,display .5s allow-discrete}@starting-style{.card{opacity:0;display:none}}

lightningcss

.card{transition:opacity .5s, display .5s allow-discrete}@starting-style{.card{opacity:0;display:none}}
Subtotal 0 / 2 2 / 2 1 / 2 2 / 2 2 / 2 1 / 2

supports

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Spaces inside the `@supports` condition parentheses are removable. Uses a property without universal support so the `@supports` wrapper cannot be statically elided.

Source

@supports (hanging-punctuation: first) {
  a {
    hanging-punctuation: first;
  }
}

Expected

@supports (hanging-punctuation:first){a{hanging-punctuation:first}}

Outputs

clean-css

@supports (hanging-punctuation:first){a{hanging-punctuation:first}}

csskit

@supports(hanging-punctuation:first){a{hanging-punctuation:first}}

cssnano

@supports (hanging-punctuation:first){a{hanging-punctuation:first}}

csso

@supports (hanging-punctuation:first){a{hanging-punctuation:first}}

esbuild

@supports (hanging-punctuation: first){a{hanging-punctuation:first}}

lightningcss

@supports (hanging-punctuation:first){a{hanging-punctuation:first}}
0002
Details

An @supports rule with no content should be removed entirely, like empty style rules.

Source

@supports (display: grid) {
}
a {
  color: red;
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

@supports(display:grid){}a{color:red}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0003
Details

`display: grid` is supported in all current browsers. The `@supports` wrapper always evaluates to true and can be removed, unwrapping the inner rules.

Source

@supports (display: grid) {
  a {
    display: grid;
  }
}

Expected

a{display:grid}

Outputs

clean-css

@supports (display:grid){a{display:grid}}

csskit

@supports(display:grid){a{display:grid}}

cssnano

@supports (display:grid){a{display:grid}}

csso

@supports (display:grid){a{display:grid}}

esbuild

@supports (display: grid){a{display:grid}}

lightningcss

@supports (display:grid){a{display:grid}}
0004
Details

`display: flex` is universally supported. The `@supports` wrapper can be removed and all inner rules unwrapped to the parent context.

Source

@supports (display: flex) {
  a {
    display: flex;
  }
  b {
    color: red;
  }
}

Expected

a{display:flex}b{color:red}

Outputs

clean-css

@supports (display:flex){a{display:flex}b{color:red}}

csskit

@supports(display:flex){a{display:flex}b{color:red}}

cssnano

@supports (display:flex){a{display:flex}b{color:red}}

csso

@supports (display:flex){a{display:flex}b{color:red}}

esbuild

@supports (display: flex){a{display:flex}b{color:red}}

lightningcss

@supports (display:flex){a{display:flex}b{color:red}}
0005
Details

`hanging-punctuation` lacks universal support, so `@supports not` cannot be statically resolved. The wrapper must remain even though the negation might suggest removal.

Source

@supports not (hanging-punctuation: first) {
  a {
    text-indent: 1em;
  }
}

Expected

@supports not (hanging-punctuation:first){a{text-indent:1em}}

Outputs

clean-css

@supports not (hanging-punctuation:first){a{text-indent:1em}}

csskit

@supports not (hanging-punctuation:first){a{text-indent:1em}}

cssnano

@supports not (hanging-punctuation:first){a{text-indent:1em}}

csso

@supports not (hanging-punctuation:first){a{text-indent:1em}}

esbuild

@supports not (hanging-punctuation: first){a{text-indent:1em}}

lightningcss

@supports not (hanging-punctuation:first){a{text-indent:1em}}
Subtotal 3 / 5 1 / 5 3 / 5 3 / 5 1 / 5 3 / 5

transforms

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`translate3d(x, 0, 0)` can be simplified to `translate(x)` since the Y and Z components are zero.

Source

a {
  transform: translate3d(10px, 0, 0);
}

Expected

a{transform:translate(10px)}

Outputs

clean-css

a{transform:translate3d(10px,0,0)}

csskit

a{transform:translate3d(10px,0,0)}

cssnano

a{transform:translate3d(10px,0,0)}

csso

a{transform:translate3d(10px,0,0)}

esbuild

a{transform:translate3d(10px,0,0)}

lightningcss

a{transform:translate(10px)}
0002
Details

`translate(0, y)` can be simplified to `translateY(y)` when the X component is zero.

Source

a {
  transform: translate(0, 20px);
}

Expected

a{transform:translateY(20px)}

Outputs

clean-css

a{transform:translate(0,20px)}

csskit

a{transform:translate(0,20px)}

cssnano

a{transform:translateY(20px)}

csso

a{transform:translate(0,20px)}

esbuild

a{transform:translateY(20px)}

lightningcss

a{transform:translateY(20px)}
0003
Details

`scale(x, x)` can omit the second argument when both axes are identical: `scale(x)`.

Source

a {
  transform: scale(1.5, 1.5);
}

Expected

a{transform:scale(1.5)}

Outputs

clean-css

a{transform:scale(1.5,1.5)}

csskit

a{transform:scale(1.5,1.5)}

cssnano

a{transform:scale(1.5)}

csso

a{transform:scale(1.5,1.5)}

esbuild

a{transform:scale(1.5)}

lightningcss

a{transform:scale(1.5)}
0004
Details

`rotateZ(a)` is equivalent to `rotate(a)` and shorter. The Z axis is the default rotation axis.

Source

a {
  transform: rotateZ(45deg);
}

Expected

a{transform:rotate(45deg)}

Outputs

clean-css

a{transform:rotateZ(45deg)}

csskit

a{transform:rotateZ(45deg)}

cssnano

a{transform:rotate(45deg)}

csso

a{transform:rotateZ(45deg)}

esbuild

a{transform:rotate(45deg)}

lightningcss

a{transform:rotate(45deg)}
0005
Details

`scale3d(x, y, 1)` can be simplified to `scale(x, y)` when the Z component is 1.

Source

a {
  transform: scale3d(1.5, 2, 1);
}

Expected

a{transform:scale(1.5,2)}

Outputs

clean-css

a{transform:scale3d(1.5,2,1)}

csskit

a{transform:scale3d(1.5,2,1)}

cssnano

a{transform:scale3d(1.5,2,1)}

csso

a{transform:scale3d(1.5,2,1)}

esbuild

a{transform:scale3d(1.5,2,1)}

lightningcss

a{transform:scale(1.5,2)}
0006
Details

`rotate3d(0, 1, 0, a)` is equivalent to `rotateY(a)` and shorter.

Source

a {
  transform: rotate3d(0, 1, 0, 30deg);
}

Expected

a{transform:rotateY(30deg)}

Outputs

clean-css

a{transform:rotate3d(0,1,0,30deg)}

csskit

a{transform:rotate3d(0,1,0,30deg)}

cssnano

a{transform:rotateY(30deg)}

csso

a{transform:rotate3d(0,1,0,30deg)}

esbuild

a{transform:rotateY(30deg)}

lightningcss

a{transform:rotateY(30deg)}
0007
Details

`scale(1.5, 1)` only scales the X axis, equivalent to `scaleX(1.5)`.

Source

a {
  transform: scale(1.5, 1);
}

Expected

a{transform:scaleX(1.5)}

Outputs

clean-css

a{transform:scale(1.5,1)}

csskit

a{transform:scale(1.5,1)}

cssnano

a{transform:scaleX(1.5)}

csso

a{transform:scale(1.5,1)}

esbuild

a{transform:scaleX(1.5)}

lightningcss

a{transform:scaleX(1.5)}
0008
Details

When X and Y are zero, `translate3d` simplifies to `translateZ`.

Source

a {
  transform: translate3d(0, 0, 5px);
}

Expected

a{transform:translateZ(5px)}

Outputs

clean-css

a{transform:translate3d(0,0,5px)}

csskit

a{transform:translate3d(0,0,5px)}

cssnano

a{transform:translateZ(5px)}

csso

a{transform:translate3d(0,0,5px)}

esbuild

a{transform:translateZ(5px)}

lightningcss

a{transform:translateZ(5px)}
0009
Details

Rotation around the X axis only simplifies to `rotateX`.

Source

a {
  transform: rotate3d(1, 0, 0, 20deg);
}

Expected

a{transform:rotateX(20deg)}

Outputs

clean-css

a{transform:rotate3d(1,0,0,20deg)}

csskit

a{transform:rotate3d(1,0,0,20deg)}

cssnano

a{transform:rotateX(20deg)}

csso

a{transform:rotate3d(1,0,0,20deg)}

esbuild

a{transform:rotateX(20deg)}

lightningcss

a{transform:rotateX(20deg)}
Subtotal 0 / 9 0 / 9 7 / 9 0 / 9 7 / 9 9 / 9

values

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`bold` is equivalent to `700` and shorter as a numeric value.

Source

a {
  font-weight: bold;
}

Expected

a{font-weight:700}

Outputs

clean-css

a{font-weight:700}

csskit

a{font-weight:bold}

cssnano

a{font-weight:700}

csso

a{font-weight:700}

esbuild

a{font-weight:700}

lightningcss

a{font-weight:700}
0002
Details

`normal` is equivalent to `400` and shorter as a numeric value.

Source

a {
  font-weight: normal;
}

Expected

a{font-weight:400}

Outputs

clean-css

a{font-weight:400}

csskit

a{font-weight:normal}

cssnano

a{font-weight:400}

csso

a{font-weight:400}

esbuild

a{font-weight:400}

lightningcss

a{font-weight:400}
0003
Details

Quotes inside `url()` are optional when the URL contains no special characters. They can be removed to save bytes.

Source

a {
  background: url("image.png");
}

Expected

a{background:url(image.png)}

Outputs

clean-css

a{background:url("image.png")}

csskit

a{background:url("image.png")}

cssnano

a{background:url(image.png)}

csso

a{background:url(image.png)}

esbuild

a{background:url(image.png)}

lightningcss

a{background:url(image.png)}
0004
Details

`1.0000px` should be normalized to `1px`. Trailing fractional zeros are redundant.

Source

a {
  width: 1.0000px;
}

Expected

a{width:1px}

Outputs

clean-css

a{width:1px}

csskit

a{width:1px}

cssnano

a{width:1px}

csso

a{width:1px}

esbuild

a{width:1px}

lightningcss

a{width:1px}
0005
Details

`+1.5px` should be normalized to `1.5px`. The explicit `+` sign on positive numeric values is redundant outside of contexts that require it (e.g. `calc()`).

Source

a {
  margin: +1.5px;
}

Expected

a{margin:1.5px}

Outputs

clean-css

a{margin:+1.5px}

csskit

a{margin:1.5px}

cssnano

a{margin:1.5px}

csso

a{margin:1.5px}

esbuild

a{margin:+1.5px}

lightningcss

a{margin:1.5px}
0006
Details

`001` should be normalized to `1`. Leading zeros on integer values are redundant and can be safely stripped.

Source

a {
  z-index: 001;
}

Expected

a{z-index:1}

Outputs

clean-css

a{z-index:001}

csskit

a{z-index:1}

cssnano

a{z-index:1}

csso

a{z-index:1}

esbuild

a{z-index:001}

lightningcss

a{z-index:1}
0007
Details

`url( image.png )` should be trimmed to `url(image.png)`. Leading and trailing whitespace inside unquoted `url()` tokens is insignificant.

Source

a {
  background: url(  image.png  );
}

Expected

a{background:url(image.png)}

Outputs

clean-css

a{background:url(image.png)}

csskit

a{background:url(image.png)}

cssnano

a{background:url(image.png)}

csso

a{background:url(image.png)}

esbuild

a{background:url(image.png)}

lightningcss

a{background:url(image.png)}
0008
Details

`'hello world'` should be normalized to `"hello world"`. CSS allows both single and double quotes for strings; normalizing to one form is valid. Uses `content` to ensure quotes cannot be removed entirely.

Source

a {
  content: 'hello world';
}

Expected

a{content:"hello world"}

Outputs

clean-css

a{content:'hello world'}

csskit

a{content:"hello world"}

cssnano

a{content:"hello world"}

csso

a{content:"hello world"}

esbuild

a{content:"hello world"}

lightningcss

a{content:"hello world"}
0009
Details

`500ms` should be converted to `.5s` when the seconds representation is shorter. The leading zero is also stripped (`0.5s` -> `.5s`).

Source

a {
  transition-duration: 500ms;
}

Expected

a{transition-duration:.5s}

Outputs

clean-css

a{transition-duration:.5s}

csskit

a{transition-duration:.5s}

cssnano

a{transition-duration:.5s}

csso

a{transition-duration:500ms}

esbuild

a{transition-duration:.5s}

lightningcss

a{transition-duration:.5s}
0010
Details

`12pt` converts to `16px`. Absolute units (pt, pc, cm, mm, in, Q) have fixed ratios to px. Normalizing to px also improves gzip compression by reducing the number of distinct unit strings in the output.

Source

a {
  font-size: 12pt;
}

Expected

a{font-size:16px}

Outputs

clean-css

a{font-size:12pt}

csskit

a{font-size:16px}

cssnano

a{font-size:12pt}

csso

a{font-size:12pt}

esbuild

a{font-size:12pt}

lightningcss

a{font-size:12pt}
0011
Details

Spaces around `+` and `-` operators inside `calc()` must be preserved. Removing them would cause `50vh+10px` to be parsed as a single dimension token, or `100%-20px` to change meaning. This tests that minifiers do not over-strip.

Source

a {
  width: calc(100% - 20px);
  height: calc(50vh + 10px);
}

Expected

a{width:calc(100% - 20px);height:calc(50vh + 10px)}

Outputs

clean-css

a{width:calc(100% - 20px);height:calc(50vh + 10px)}

csskit

a{width:calc(100% - 20px);height:calc(50vh + 10px)}

cssnano

a{height:calc(50vh + 10px);width:calc(100% - 20px)}

csso

a{width:calc(100% - 20px);height:calc(50vh + 10px)}

esbuild

a{width:calc(100% - 20px);height:calc(50vh + 10px)}

lightningcss

a{width:calc(100% - 20px);height:calc(50vh + 10px)}
0012
Details

`var(--main-color)` must be preserved as-is. Custom property references cannot be resolved or optimized at minification time since their values are defined at runtime.

Source

a {
  color: var(--main-color);
}

Expected

a{color:var(--main-color)}

Outputs

clean-css

a{color:var(--main-color)}

csskit

a{color:var(--main-color)}

cssnano

a{color:var(--main-color)}

csso

a{color:var(--main-color)}

esbuild

a{color:var(--main-color)}

lightningcss

a{color:var(--main-color)}
0013
Details

Whitespace inside `linear-gradient()` around commas and arguments should be stripped. Uses a numeric angle and short hex colors to avoid confounding direction keyword or color conversions.

Source

a {
  background: linear-gradient( 45deg, #111 , #999 );
}

Expected

a{background:linear-gradient(45deg,#111,#999)}

Outputs

clean-css

a{background:linear-gradient(45deg,#111 ,#999)}

csskit

a{background:linear-gradient( 45deg,#111 ,#999)}

cssnano

a{background:linear-gradient(45deg,#111,#999)}

csso

a{background:linear-gradient(45deg,#111,#999)}

esbuild

a{background:linear-gradient(45deg,#111,#999)}

lightningcss

a{background:linear-gradient(45deg,#111,#999)}
0014
Details

`calc(calc(100% - 20px) + 10px)` should flatten the inner `calc()` and reduce compatible units: `-20px + 10px` = `-10px`, yielding `calc(100% - 10px)`.

Source

a {
  width: calc(calc(100% - 20px) + 10px);
}

Expected

a{width:calc(100% - 10px)}

Outputs

clean-css

a{width:calc(calc(100% - 20px) + 10px)}

csskit

a{width:calc(calc(100% - 20px)+ 10px)}

cssnano

a{width:calc(100% - 10px)}

csso

a{width:calc(calc(100% - 20px) + 10px)}

esbuild

a{width:calc(100% - 10px)}

lightningcss

a{width:calc(100% - 10px)}
0015
Details

`calc(100px * 2)` can be fully resolved to `200px` at compile time since all values are known.

Source

a {
  width: calc(100px * 2);
}

Expected

a{width:200px}

Outputs

clean-css

a{width:calc(100px * 2)}

csskit

a{width:calc(100px * 2)}

cssnano

a{width:200px}

csso

a{width:calc(100px*2)}

esbuild

a{width:200px}

lightningcss

a{width:200px}
0016
Details

`calc(50px + 50px)` can be resolved to `100px` since both operands share the same unit.

Source

a {
  width: calc(50px + 50px);
}

Expected

a{width:100px}

Outputs

clean-css

a{width:calc(50px + 50px)}

csskit

a{width:calc(50px + 50px)}

cssnano

a{width:100px}

csso

a{width:calc(50px + 50px)}

esbuild

a{width:100px}

lightningcss

a{width:100px}
0017
Details

Complex calc expressions with compatible units are partially reduced: px terms are combined while percentage terms are preserved.

Source

a {
  width: calc(((75.37% - 63.5px) - 900px) + (2 * 100px));
}

Expected

a{width:calc(75.37% - 763.5px)}

Outputs

clean-css

a{width:calc(((75.37% - 63.5px) - 900px) + (2 * 100px))}

csskit

a{width:calc(((75.37% - 63.5px)- 900px)+ (2 * 100px))}

cssnano

a{width:calc(75.37% - 763.5px)}

csso

a{width:calc(((75.37% - 63.5px) - 900px) + (2*100px))}

esbuild

a{width:calc(75.37% - 763.5px)}

lightningcss

a{width:calc(75.37% - 763.5px)}
0018
Details

`0.50000` should be reduced to `.5` by removing trailing zeros and leading zero.

Source

a {
  opacity: 0.50000;
}

Expected

a{opacity:.5}

Outputs

clean-css

a{opacity:.5}

csskit

a{opacity:.5}

cssnano

a{opacity:.5}

csso

a{opacity:.5}

esbuild

a{opacity:.5}

lightningcss

a{opacity:.5}
0019
Details

Extra whitespace inside grid-template-areas strings is collapsed, and consecutive dots (`....`) for unnamed cells are reduced to a single dot.

Source

a {
  grid-template-areas: "head  head"
                       "nav   main"
                       "foot  ....";
}

Expected

a{grid-template-areas:"head head""nav main""foot ."}

Outputs

clean-css

a{grid-template-areas:"head  head" "nav   main" "foot  ...."}

csskit

a{grid-template-areas:"head  head""nav   main""foot  ...."}

cssnano

a{grid-template-areas:"head  head" "nav   main" "foot  ...."}

csso

a{grid-template-areas:"head  head""nav   main""foot  ...."}

esbuild

a{grid-template-areas:"head  head" "nav   main" "foot  ...."}

lightningcss

a{grid-template-areas:"head head""nav main""foot."}
0020
Details

`center center` resolves to `50%` -- when both axes are the same, a single value suffices.

Source

a {
  background-position: center center;
}

Expected

a{background-position:50%}

Outputs

clean-css

a{background-position:center center}

csskit

a{background-position:center center}

cssnano

a{background-position:50%}

csso

a{background-position:center center}

esbuild

a{background-position:center center}

lightningcss

a{background-position:50%}
0021
Details

`calc(100% / 3)` cannot be statically resolved to a finite decimal without precision loss. Any truncation (e.g. `33.3333%`) introduces measurable rounding error. The calc wrapper must be preserved; only whitespace is removable.

Source

a {
  width: calc(100% / 3);
}

Expected

a{width:calc(100%/3)}

Outputs

clean-css

a{width:calc(100% / 3)}

csskit

a{width:calc(100% / 3)}

cssnano

a{width:33.33333%}

csso

a{width:calc(100%/3)}

esbuild

a{width:calc(100% / 3)}

lightningcss

a{width:33.3333%}
0022
Details

`display: block flow` is the two-value syntax equivalent of `display: block`.

Source

a {
  display: block flow;
}

Expected

a{display:block}

Outputs

clean-css

a{display:block flow}

csskit

a{display:block flow}

cssnano

a{display:block}

csso

a{display:block flow}

esbuild

a{display:block flow}

lightningcss

a{display:block}
0023
Details

`cubic-bezier(0, 0, 1, 1)` is the functional form of `linear`.

Source

a {
  transition: color cubic-bezier(0, 0, 1, 1);
}

Expected

a{transition:color linear}

Outputs

clean-css

a{transition:color cubic-bezier(0, 0, 1, 1)}

csskit

a{transition:color cubic-bezier(0,0,1,1)}

cssnano

a{transition:color linear}

csso

a{transition:color cubic-bezier(0,0,1,1)}

esbuild

a{transition:color cubic-bezier(0,0,1,1)}

lightningcss

a{transition:color cubic-bezier(0,0,1,1)}
0024
Details

`cubic-bezier(0.25, 0.1, 0.25, 1)` is the functional form of `ease`.

Source

a {
  transition: color cubic-bezier(0.25, 0.1, 0.25, 1);
}

Expected

a{transition:color ease}

Outputs

clean-css

a{transition:color cubic-bezier(.25, .1, .25, 1)}

csskit

a{transition:color cubic-bezier(.25,.1,.25,1)}

cssnano

a{transition:color ease}

csso

a{transition:color cubic-bezier(.25,.1,.25,1)}

esbuild

a{transition:color cubic-bezier(.25,.1,.25,1)}

lightningcss

a{transition:color}
0025
Details

`no-repeat no-repeat` collapses to `no-repeat` when both axes are identical.

Source

a {
  background-repeat: no-repeat no-repeat;
}

Expected

a{background-repeat:no-repeat}

Outputs

clean-css

a{background-repeat:no-repeat no-repeat}

csskit

a{background-repeat:no-repeat no-repeat}

cssnano

a{background-repeat:no-repeat}

csso

a{background-repeat:no-repeat no-repeat}

esbuild

a{background-repeat:no-repeat no-repeat}

lightningcss

a{background-repeat:no-repeat}
0026
Details

`repeat no-repeat` is equivalent to the `repeat-x` keyword.

Source

a {
  background-repeat: repeat no-repeat;
}

Expected

a{background-repeat:repeat-x}

Outputs

clean-css

a{background-repeat:repeat no-repeat}

csskit

a{background-repeat:repeat no-repeat}

cssnano

a{background-repeat:repeat-x}

csso

a{background-repeat:repeat no-repeat}

esbuild

a{background-repeat:repeat no-repeat}

lightningcss

a{background-repeat:repeat-x}
0027
Details

`0.25turn` equals `90deg`. Degrees are shorter for common turn fractions.

Source

a {
  transform: rotate(0.25turn);
}

Expected

a{transform:rotate(90deg)}

Outputs

clean-css

a{transform:rotate(.25turn)}

csskit

a{transform:rotate(.25turn)}

cssnano

a{transform:rotate(90deg)}

csso

a{transform:rotate(.25turn)}

esbuild

a{transform:rotate(.25turn)}

lightningcss

a{transform:rotate(.25turn)}
0028
Details

The initial value of `min-width` is `auto` (CSS Sizing Level 3), not `0`. `auto` is shorter than `initial` and semantically equivalent.

Source

a {
  min-width: initial;
}

Expected

a{min-width:auto}

Outputs

clean-css

a{min-width:initial}

csskit

a{min-width:initial}

cssnano

a{min-width:auto}

csso

a{min-width:initial}

esbuild

a{min-width:initial}

lightningcss

a{min-width:initial}
0029
Details

`border: solid 1px red` should be reordered to `border: 1px solid red` (width style color).

Source

a {
  border: solid 1px red;
}

Expected

a{border:1px solid red}

Outputs

clean-css

a{border:1px solid red}

csskit

a{border:1px solid red}

cssnano

a{border:1px solid red}

csso

a{border:solid 1px red}

esbuild

a{border:solid 1px red}

lightningcss

a{border:1px solid red}
0030
Details

`flex-flow: wrap row` should be reordered to `flex-flow: row wrap` (direction then wrap).

Source

a {
  flex-flow: wrap row;
}

Expected

a{flex-flow:row wrap}

Outputs

clean-css

a{flex-flow:wrap row}

csskit

a{flex-flow:row wrap}

cssnano

a{flex-flow:row wrap}

csso

a{flex-flow:wrap row}

esbuild

a{flex-flow:wrap row}

lightningcss

a{flex-flow:wrap}
0031
Details

Quotes around font-family names can be removed when the name is a valid CSS identifier sequence.

Source

a {
  font-family: "Helvetica Neue";
}

Expected

a{font-family:Helvetica Neue}

Outputs

clean-css

a{font-family:"Helvetica Neue"}

csskit

a{font-family:"Helvetica Neue"}

cssnano

a{font-family:Helvetica Neue}

csso

a{font-family:"Helvetica Neue"}

esbuild

a{font-family:Helvetica Neue}

lightningcss

a{font-family:Helvetica Neue}
0032
Details

Duplicate font names in a font-family list can be removed.

Source

a {
  font-family: Helvetica, Arial, Helvetica, sans-serif;
}

Expected

a{font-family:Helvetica,Arial,sans-serif}

Outputs

clean-css

a{font-family:Helvetica,Arial,Helvetica,sans-serif}

csskit

a{font-family:Helvetica,Arial,Helvetica,sans-serif}

cssnano

a{font-family:Helvetica,Arial,sans-serif}

csso

a{font-family:Helvetica,Arial,Helvetica,sans-serif}

esbuild

a{font-family:Helvetica,Arial,Helvetica,sans-serif}

lightningcss

a{font-family:Helvetica,Arial,sans-serif}
0033
Details

`display: inline flow-root` is the two-value equivalent of `inline-block`.

Source

a {
  display: inline flow-root;
}

Expected

a{display:inline-block}

Outputs

clean-css

a{display:inline flow-root}

csskit

a{display:inline flow-root}

cssnano

a{display:inline-block}

csso

a{display:inline flow-root}

esbuild

a{display:inline flow-root}

lightningcss

a{display:inline-block}
0034
Details

`steps(1, start)` is the functional form of the `step-start` timing keyword.

Source

a {
  animation: fade steps(1, start);
}

Expected

a{animation:fade step-start}

Outputs

clean-css

a{animation:steps(1,start) fade}

csskit

a{animation:fade steps(1,start)}

cssnano

a{animation:fade step-start}

csso

a{animation:fade steps(1,start)}

esbuild

a{animation:fade steps(1,start)}

lightningcss

a{animation:step-start fade}
0035
Details

`left top` is equivalent to `0 0` and shorter.

Source

a {
  background-position: left top;
}

Expected

a{background-position:0 0}

Outputs

clean-css

a{background-position:left top}

csskit

a{background-position:left top}

cssnano

a{background-position:0 0}

csso

a{background-position:left top}

esbuild

a{background-position:left top}

lightningcss

a{background-position:0 0}
0036
Details

`cubic-bezier(0.42, 0, 1, 1)` is the functional form of `ease-in`.

Source

a {
  transition: color cubic-bezier(0.42, 0, 1, 1);
}

Expected

a{transition:color ease-in}

Outputs

clean-css

a{transition:color cubic-bezier(.42, 0, 1, 1)}

csskit

a{transition:color cubic-bezier(.42,0,1,1)}

cssnano

a{transition:color ease-in}

csso

a{transition:color cubic-bezier(.42,0,1,1)}

esbuild

a{transition:color cubic-bezier(.42,0,1,1)}

lightningcss

a{transition:color ease-in}
0037
Details

`cubic-bezier(0, 0, 0.58, 1)` is the functional form of `ease-out`.

Source

a {
  transition: color cubic-bezier(0, 0, 0.58, 1);
}

Expected

a{transition:color ease-out}

Outputs

clean-css

a{transition:color cubic-bezier(0, 0, .58, 1)}

csskit

a{transition:color cubic-bezier(0,0,.58,1)}

cssnano

a{transition:color ease-out}

csso

a{transition:color cubic-bezier(0,0,.58,1)}

esbuild

a{transition:color cubic-bezier(0,0,.58,1)}

lightningcss

a{transition:color ease-out}
0038
Details

`no-repeat repeat` is equivalent to the `repeat-y` keyword.

Source

a {
  background-repeat: no-repeat repeat;
}

Expected

a{background-repeat:repeat-y}

Outputs

clean-css

a{background-repeat:no-repeat repeat}

csskit

a{background-repeat:no-repeat repeat}

cssnano

a{background-repeat:repeat-y}

csso

a{background-repeat:no-repeat repeat}

esbuild

a{background-repeat:no-repeat repeat}

lightningcss

a{background-repeat:repeat-y}
0039
Details

Multiplying a value by 1 has no effect. `calc(100px * 1)` simplifies to `100px`, removing the calc() wrapper entirely.

Source

a {
  width: calc(100px * 1);
}

Expected

a{width:100px}

Outputs

clean-css

a{width:calc(100px * 1)}

csskit

a{width:calc(100px * 1)}

cssnano

a{width:100px}

csso

a{width:calc(100px*1)}

esbuild

a{width:100px}

lightningcss

a{width:100px}
0040
Details

Adding zero of the same unit has no effect. `calc(100px + 0px)` simplifies to `100px`, removing the calc() wrapper. Note: a zero term can only be combined with a term of the same unit, not removed outright.

Source

a {
  width: calc(100px + 0px);
}

Expected

a{width:100px}

Outputs

clean-css

a{width:calc(100px + 0px)}

csskit

a{width:calc(100px + 0px)}

cssnano

a{width:100px}

csso

a{width:calc(100px + 0px)}

esbuild

a{width:100px}

lightningcss

a{width:100px}
0041
Details

`calc(-1 * (-10px))` negates a negative value. The product -1 \* -10px = 10px, fully resolving to a plain length value.

Source

a {
  margin-left: calc(-1 * (-10px));
}

Expected

a{margin-left:10px}

Outputs

clean-css

a{margin-left:calc(-1 * (-10px))}

csskit

a{margin-left:calc(-1 * (-10px))}

cssnano

a{margin-left:10px}

csso

a{margin-left:calc(-1*(-10px))}

esbuild

a{margin-left:10px}

lightningcss

a{margin-left:10px}
0042
Details

Dividing by a reciprocal cancels out. `calc(1 / (1 / 50px))` simplifies back to `50px` because the two inversions undo each other.

Source

a {
  width: calc(1 / (1 / 50px));
}

Expected

a{width:50px}

Outputs

clean-css

a{width:calc(1 / (1 / 50px))}

csskit

a{width:calc(1 / (1 / 50px))}

cssnano

a{width:calc(1 / (1 / 50px))}

csso

a{width:calc(1/(1/50px))}

esbuild

a{width:calc(1/(1/50px))}

lightningcss

a{width:calc(1 / (1 / 50px))}
0043
Details

Nested additions inside calc() are flattened into a single addition. Two nested calc() sums are merged and all px terms are combined: `10 + 20 + 30 + 40 = 100px`.

Source

a {
  width: calc(calc(10px + 20px) + calc(30px + 40px));
}

Expected

a{width:100px}

Validate

Outputs

clean-css

a{width:calc(calc(10px + 20px) + calc(30px + 40px))}

csskit

a{width:calc(calc(10px + 20px)+ calc(30px + 40px))}

cssnano

a{width:100px}

csso

a{width:calc(calc(10px + 20px) + calc(30px + 40px))}

esbuild

a{width:100px}

lightningcss

a{width:100px}
0044
Details

When a multiplication chain contains multiple plain numbers, they are multiplied together first. `2 * 3` becomes `6`, then `6 * 10px` = `60px`.

Source

a {
  width: calc(2 * 3 * 10px);
}

Expected

a{width:60px}

Outputs

clean-css

a{width:calc(2 * 3 * 10px)}

csskit

a{width:calc(2 * 3 * 10px)}

cssnano

a{width:60px}

csso

a{width:calc(2*3*10px)}

esbuild

a{width:60px}

lightningcss

a{width:60px}
0045
Details

Multiplication distributes over addition. `2 * (50px + 25px)` expands to `100px + 50px`, then the like terms combine to `150px`.

Source

a {
  width: calc(2 * (50px + 25px));
}

Expected

a{width:150px}

Validate

Outputs

clean-css

a{width:calc(2 * (50px + 25px))}

csskit

a{width:calc(2 * (50px + 25px))}

cssnano

a{width:150px}

csso

a{width:calc(2*(50px + 25px))}

esbuild

a{width:150px}

lightningcss

a{width:150px}
0046
Details

Terms with the same unit are combined by adding their values. `calc(50% + 25%)` becomes `75%`, removing the calc() wrapper.

Source

a {
  width: calc(50% + 25%);
}

Expected

a{width:75%}

Outputs

clean-css

a{width:calc(50% + 25%)}

csskit

a{width:calc(50% + 25%)}

cssnano

a{width:75%}

csso

a{width:calc(50% + 25%)}

esbuild

a{width:75%}

lightningcss

a{width:75%}
0047
Details

`calc(10 / 2)` divides two unitless numbers, yielding `5`. Since line-height accepts unitless numbers, the result is a plain `5` with the calc() wrapper removed.

Source

a {
  line-height: calc(10 / 2);
}

Expected

a{line-height:5}

Outputs

clean-css

a{line-height:calc(10 / 2)}

csskit

a{line-height:calc(10 / 2)}

cssnano

a{line-height:5}

csso

a{line-height:calc(10/2)}

esbuild

a{line-height:5}

lightningcss

a{line-height:5}
0048
Details

`calc(50px - 50px)` subtracts identical values. The same-unit terms cancel out: `50 - 50 = 0`. The result is `0` (unitless zero is valid for lengths).

Source

a {
  width: calc(50px - 50px);
}

Expected

a{width:0}

Outputs

clean-css

a{width:calc(50px - 50px)}

csskit

a{width:calc(50px - 50px)}

cssnano

a{width:0}

csso

a{width:calc(50px - 50px)}

esbuild

a{width:0px}

lightningcss

a{width:0}
0049
Details

`calc(100px * 0)` multiplies a dimension by zero. The result is `0px`, which serializes as `0`.

Source

a {
  width: calc(100px * 0);
}

Expected

a{width:0}

Outputs

clean-css

a{width:calc(100px * 0)}

csskit

a{width:calc(100px * 0)}

cssnano

a{width:0}

csso

a{width:calc(100px*0)}

esbuild

a{width:0px}

lightningcss

a{width:0}
0050
Details

`pi` is a numeric constant (~3.14159). `calc(pi * 10px)` resolves the constant then multiplies, yielding `31.4159px`.

Source

a {
  width: calc(pi * 100px);
}

Expected

a{width:314.159px}

Validate

Outputs

clean-css

a{width:calc(pi * 100px)}

csskit

a{width:calc(pi * 100px)}

cssnano

a{width:calc(pi*100px)}

csso

a{width:calc(pi*100px)}

esbuild

a{width:calc(pi*100px)}

lightningcss

a{width:314.159px}
0051
Details

`e` is Euler's number (~2.71828). `calc(e * 10px)` resolves the constant then multiplies, yielding `27.1828px`.

Source

a {
  width: calc(e * 100px);
}

Expected

a{width:271.828px}

Validate

Outputs

clean-css

a{width:calc(e * 100px)}

csskit

a{width:calc(e * 100px)}

cssnano

a{width:calc(e*100px)}

csso

a{width:calc(e*100px)}

esbuild

a{width:calc(e*100px)}

lightningcss

a{width:271.828px}
0052
Details

When all arguments to min() are known comparable values, the function can be fully resolved. `min(10px, 20px)` resolves to `10px`.

Source

a {
  width: min(10px, 20px);
}

Expected

a{width:10px}

Outputs

clean-css

a{width:min(10px,20px)}

csskit

a{width:min(10px,20px)}

cssnano

a{width:min(10px,20px)}

csso

a{width:min(10px,20px)}

esbuild

a{width:min(10px,20px)}

lightningcss

a{width:10px}
0053
Details

Compatible units convert to canonical units: `1in` = `96px`. The sum combines `96px + 0px` = `96px`, resolving to a plain length value.

Source

a {
  width: calc(1in + 0px);
}

Expected

a{width:96px}

Validate

Outputs

clean-css

a{width:calc(1in + 0px)}

csskit

a{width:calc(1in + 0px)}

cssnano

a{width:1in}

csso

a{width:calc(1in + 0px)}

esbuild

a{width:calc(1in + 0px)}

lightningcss

a{width:96px}
0054
Details

`calc(100px)` contains a single numeric value with no operations. The calc() wrapper is unnecessary and should be removed, yielding `100px`.

Source

a {
  width: calc(100px);
}

Expected

a{width:100px}

Outputs

clean-css

a{width:calc(100px)}

csskit

a{width:calc(100px)}

cssnano

a{width:100px}

csso

a{width:calc(100px)}

esbuild

a{width:100px}

lightningcss

a{width:100px}
0055
Details

`calc(100px / 4)` creates a Product of `100px` and Invert(`4`). The Invert resolves `4` to `0.25`, then the Product yields `25px`. Fully resolved, no calc() needed.

Source

a {
  width: calc(100px / 4);
}

Expected

a{width:25px}

Validate

Outputs

clean-css

a{width:calc(100px / 4)}

csskit

a{width:calc(100px / 4)}

cssnano

a{width:25px}

csso

a{width:calc(100px/4)}

esbuild

a{width:25px}

lightningcss

a{width:25px}
0056
Details

`calc(100% - 50px + 0%)` has both percentage and px terms. Like units are combined: `100% + 0%` = `100%`, while `-50px` remains unchanged. The result is `calc(100% - 50px)` -- calc() is preserved because % and px cannot be resolved together at specified-value time.

Source

a {
  width: calc(100% - 50px + 0%);
}

Expected

a{width:calc(100% - 50px)}

Validate

Outputs

clean-css

a{width:calc(100% - 50px + 0%)}

csskit

a{width:calc(100% - 50px + 0%)}

cssnano

a{width:calc(100% - 50px)}

csso

a{width:calc(100% - 50px + 0%)}

esbuild

a{width:calc(100% - 50px)}

lightningcss

a{width:calc(100% - 50px)}
0057
Details

Custom property values may be read by JavaScript via `getComputedStyle`, where the serialized form matters. Converting `rgb(0 0 0)` to `#000` inside a custom property changes JS-visible serialization.

Source

a {
  --brand-color: rgb(0 0 0);
}

Expected

a{--brand-color: rgb(0 0 0)}

Outputs

clean-css

a{--brand-color:rgb(0 0 0)}

csskit

a{--brand-color:rgb(0 0 0)}

cssnano

a{--brand-color:#000}

csso

a{--brand-color:rgb(0 0 0)}

esbuild

a{--brand-color: rgb(0 0 0)}

lightningcss

a{--brand-color:#000}
0058
Details

`--foo: ;` is valid CSS that sets a custom property to an empty value (a single space token). This is used to "reset" inherited custom properties. Discarding this declaration as "empty" changes behavior because `var(--foo, red)` would then use the fallback instead.

Source

a {
  --foo: ;
  color: var(--foo, red);
}

Expected

a{--foo: ;color:var(--foo,red)}

Outputs

clean-css

a{--foo: ;color:var(--foo,red)}

csskit

a{--foo:color:var(--foo,red)}

cssnano

a{--foo: ;color:var(--foo,red)}

csso

a{--foo: ;color:var(--foo, red)}

esbuild

a{--foo: ;color:var(--foo, red)}

lightningcss

a{--foo: ;color:var(--foo,red)}
0059
Details

A rule with unrecognized property names is syntactically valid CSS. Minifiers must not discard it -- unknown properties may be meaningful to other tools or future CSS specifications.

Source

a {
  foo: bar;
}
b {
  color: red;
}

Expected

a{foo:bar}b{color:red}

Outputs

clean-css

a{foo:bar}b{color:red}

csskit

a{foo:bar}b{color:red}

cssnano

a{foo:bar}b{color:red}

csso

a{foo:bar}b{color:red}

esbuild

a{foo:bar}b{color:red}

lightningcss

a{foo:bar}b{color:red}
0060
Details

Removing quotes from `url("image (1).png")` would produce `url(image (1).png)` which is a parse error -- the unquoted form cannot contain `(` or `)`.

Source

a {
  background: url("image (1).png");
}

Expected

a{background:url("image (1).png")}

Outputs

clean-css

a{background:url("image (1).png")}

csskit

a{background:url("image (1).png")}

cssnano

a{background:url("image (1).png")}

csso

a{background:url("image (1).png")}

esbuild

a{background:url("image (1).png")}

lightningcss

a{background:url("image (1).png")}
0061
Details

`content: ""` generates a pseudo-element with no text. Removing the declaration or changing the value to `none`/`normal` would prevent the pseudo-element from being generated, which is a semantic change.

Source

a:before {
  content: "";
}

Expected

a:before{content:""}

Outputs

clean-css

a:before{content:""}

csskit

a:before{content:""}

cssnano

a:before{content:""}

csso

a:before{content:""}

esbuild

a:before{content:""}

lightningcss

a:before{content:""}
0062
Details

When min, preferred, and max are all the same known value, `clamp()` can be replaced with that value.

Source

a {
  width: clamp(10px, 10px, 10px);
}

Expected

a{width:10px}

Outputs

clean-css

a{width:clamp(10px,10px,10px)}

csskit

a{width:clamp(10px,10px,10px)}

cssnano

a{width:clamp(10px,10px,10px)}

csso

a{width:clamp(10px,10px,10px)}

esbuild

a{width:clamp(10px,10px,10px)}

lightningcss

a{width:10px}
0063
Details

When all arguments to `max()` are static comparable values, the function can be replaced with the largest value.

Source

a {
  width: max(10px, 20px);
}

Expected

a{width:20px}

Outputs

clean-css

a{width:max(10px,20px)}

csskit

a{width:max(10px,20px)}

cssnano

a{width:max(10px,20px)}

csso

a{width:max(10px,20px)}

esbuild

a{width:max(10px,20px)}

lightningcss

a{width:20px}
0064
Details

The initial value of `opacity` is `1`, which is shorter than `initial`.

Source

a {
  opacity: initial;
}

Expected

a{opacity:1}

Outputs

clean-css

a{opacity:initial}

csskit

a{opacity:initial}

cssnano

a{opacity:1}

csso

a{opacity:initial}

esbuild

a{opacity:initial}

lightningcss

a{opacity:initial}
0065
Details

The initial value of `display` is `inline`, which is the same length as `initial`. No replacement saves bytes.

Source

a {
  display: initial;
}

Expected

a{display:initial}

Outputs

clean-css

a{display:initial}

csskit

a{display:initial}

cssnano

a{display:initial}

csso

a{display:initial}

esbuild

a{display:initial}

lightningcss

a{display:initial}
0066
Details

The initial value of `z-index` is `auto`, which is shorter than `initial`.

Source

a {
  z-index: initial;
}

Expected

a{z-index:auto}

Outputs

clean-css

a{z-index:initial}

csskit

a{z-index:initial}

cssnano

a{z-index:auto}

csso

a{z-index:initial}

esbuild

a{z-index:initial}

lightningcss

a{z-index:initial}
0067
Details

The initial value of `margin` is `0`, which is shorter than `initial`.

Source

a {
  margin: initial;
}

Expected

a{margin:0}

Outputs

clean-css

a{margin:initial}

csskit

a{margin:initial}

cssnano

a{margin:initial}

csso

a{margin:initial}

esbuild

a{margin:initial}

lightningcss

a{margin:initial}
0068
Details

The initial value of `background-color` is `transparent`, which is longer than `initial`. No replacement saves bytes.

Source

a {
  background-color: initial;
}

Expected

a{background-color:initial}

Outputs

clean-css

a{background-color:initial}

csskit

a{background-color:initial}

cssnano

a{background-color:initial}

csso

a{background-color:initial}

esbuild

a{background-color:initial}

lightningcss

a{background-color:initial}
0069
Details

`calc(100px + 0%)` must not simplify to `100px`. Although `0%` is numerically zero, it has a different unit type. The browser resolves `%` against the containing block at computed value time, and future animations or transitions could interpolate through non-zero percentage values.

Source

a {
  width: calc(100px + 0%);
}

Expected

a{width:calc(100px + 0%)}

Outputs

clean-css

a{width:calc(100px + 0%)}

csskit

a{width:calc(100px + 0%)}

cssnano

a{width:100px}

csso

a{width:calc(100px + 0%)}

esbuild

a{width:calc(100px + 0%)}

lightningcss

a{width:100px}
0070
Details

When `@property` declares `syntax: "<color>"`, the value is parsed as a color at parse time. The serialization is well-defined, so `rgb(0 0 0)` can be safely minified to `#000`.

Source

@property --brand-color {
  syntax: "<color>";
  inherits: false;
  initial-value: #000;
}
a {
  --brand-color: rgb(0 0 0);
}

Expected

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}a{--brand-color:#000}

Outputs

clean-css

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}a{--brand-color:rgb(0 0 0)}

csskit

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}a{--brand-color:rgb(0 0 0)}

cssnano

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}a{--brand-color:#000}

csso

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}a{--brand-color:rgb(0 0 0)}

esbuild

@property --brand-color{syntax: "<color>"; inherits: false; initial-value: #000;}a{--brand-color: rgb(0 0 0)}

lightningcss

@property --brand-color{syntax:"<color>";inherits:false;initial-value:#000}a{--brand-color:#000}
Subtotal 16 / 70 18 / 70 56 / 70 19 / 70 34 / 70 55 / 70

whitespace

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

Remove newlines, indentation, and spaces around braces and colons.

Source

a {
  color: red;
}

Expected

a{color:red}

Outputs

clean-css

a{color:red}

csskit

a{color:red}

cssnano

a{color:red}

csso

a{color:red}

esbuild

a{color:red}

lightningcss

a{color:red}
0002
Details

Blank lines between rules must be collapsed. No separator is needed between `}` and the next selector.

Source

a {
  margin: 0;
}

b {
  padding: 0;
}

Expected

a{margin:0}b{padding:0}

Outputs

clean-css

a{margin:0}b{padding:0}

csskit

a{margin:0}b{padding:0}

cssnano

a{margin:0}b{padding:0}

csso

a{margin:0}b{padding:0}

esbuild

a{margin:0}b{padding:0}

lightningcss

a{margin:0}b{padding:0}
0003
Details

Values split across lines with tab indentation must collapse correctly.

Source

a {
  color: red;
  font-size: 16px;
}

Expected

a{color:red;font-size:16px}

Outputs

clean-css

a{color:red;font-size:16px}

csskit

a{color:red;font-size:16px}

cssnano

a{color:red;font-size:16px}

csso

a{color:red;font-size:16px}

esbuild

a{color:red;font-size:16px}

lightningcss

a{color:red;font-size:16px}
0004
Details

Spaces around commas in selector lists (`a , b`) must be removed.

Source

a,
b {
  color: red;
}

Expected

a,b{color:red}

Outputs

clean-css

a,b{color:red}

csskit

a,b{color:red}

cssnano

a,b{color:red}

csso

a,b{color:red}

esbuild

a,b{color:red}

lightningcss

a,b{color:red}
0005
Details

The semicolon after the last declaration in a rule can be dropped.

Source

a {
  color: red;
  margin: 0;
  padding: 10px;
}

Expected

a{color:red;margin:0;padding:10px}

Outputs

clean-css

a{color:red;margin:0;padding:10px}

csskit

a{color:red;margin:0;padding:10px}

cssnano

a{color:red;margin:0;padding:10px}

csso

a{color:red;margin:0;padding:10px}

esbuild

a{color:red;margin:0;padding:10px}

lightningcss

a{color:red;margin:0;padding:10px}
0006
Details

`color: red;;` should collapse redundant semicolons. Multiple consecutive semicolons are syntactically valid but wasteful; only one is needed between declarations, and the final one before `}` can also be dropped.

Source

a {
  color: red;;
  margin: 0;;;
}

Expected

a{color:red;margin:0}

Outputs

clean-css

a{color:red;margin:0}

csskit

a{color:red;margin:0}

cssnano

a{color:red;margin:0}

csso

a{color:red;margin:0}

esbuild

a{color:red;margin:0}

lightningcss

a{color:red;margin:0}
0007
Details

`@media not print` requires a space between `not` and `print`. Without it, `notprint` becomes a single ident token parsed as an unknown media type, changing the query from "not print media" to "unknown media type" which never matches.

Source

@media not print {
  a {
    color: red;
  }
}

Expected

@media not print{a{color:red}}

Validate

Outputs

clean-css

@media not print{a{color:red}}

csskit

@media not print{a{color:red}}

cssnano

@media not print{a{color:red}}

csso

@media not print{a{color:red}}

esbuild

@media not print{a{color:red}}

lightningcss

@media not print{a{color:red}}
0008
Details

`@media screen and (color)` requires spaces around `and`. Without a space before `and`, `screenand` becomes a single ident token (unknown media type). Without a space after `and`, `and(` becomes a function token instead of the `and` keyword followed by `(`, making the query invalid.

Source

@media screen and (color) {
  a {
    color: red;
  }
}

Expected

@media screen and (color){a{color:red}}

Validate

Outputs

clean-css

@media screen and (color){a{color:red}}

csskit

@media screen and (color){a{color:red}}

cssnano

@media screen and (color){a{color:red}}

csso

@media screen and (color){a{color:red}}

esbuild

@media screen and (color){a{color:red}}

lightningcss

@media screen and (color){a{color:red}}
0009
Details

`@supports not (display: flex)` requires a space between `not` and `(`. The CSS tokenizer treats `not(` as a function token, not the `not` keyword followed by `(`. This changes the grammar from a negation condition to an invalid/unknown function call, causing the condition to fail.

Source

@supports not (display: flex) {
  a {
    display: block;
  }
}

Expected

@supports not (display:flex){a{display:block}}

Validate

Outputs

clean-css

@supports not (display:flex){a{display:block}}

csskit

@supports not (display:flex){a{display:block}}

cssnano

@supports not (display:flex){a{display:block}}

csso

@supports not (display:flex){a{display:block}}

esbuild

@supports not (display: flex){a{display:block}}

lightningcss

@supports not (display:flex){a{display:block}}
0010
Details

`@supports (X) and (Y)` requires spaces around `and`. Without a space after `and`, the tokenizer produces a function token `and(` instead of the keyword `and` followed by `(`, causing the entire @supports rule to be dropped by the parser.

Source

@supports (display: grid) and (gap: 10px) {
  a {
    display: grid;
    gap: 10px;
  }
}

Expected

@supports (display:grid) and (gap:10px){a{display:grid;gap:10px}}

Validate

Outputs

clean-css

@supports (display:grid) and (gap:10px){a{display:grid;gap:10px}}

csskit

@supports (display:grid)and (gap:10px){a{display:grid; gap:10px}}

cssnano

@supports (display:grid) and (gap:10px){a{display:grid;gap:10px}}

csso

@supports (display:grid) and (gap:10px){a{display:grid;gap:10px}}

esbuild

@supports (display: grid) and (gap: 10px){a{display:grid;gap:10px}}

lightningcss

@supports (display:grid) and (gap:10px){a{gap:10px;display:grid}}
0011
Details

Spaces around `+` inside `calc()` are required by the CSS spec, just like `-`. Without the space, `+20px` is tokenized as a single positive dimension token, leaving two values with no operator, which is invalid and causes the declaration to be silently dropped.

Source

a {
  width: calc(100% + 20px);
}

Expected

a{width:calc(100% + 20px)}

Validate

Outputs

clean-css

a{width:calc(100% + 20px)}

csskit

a{width:calc(100% + 20px)}

cssnano

a{width:calc(100% + 20px)}

csso

a{width:calc(100% + 20px)}

esbuild

a{width:calc(100% + 20px)}

lightningcss

a{width:calc(100% + 20px)}
0012
Details

Unlike `+` and `-`, the `*` and `/` operators in `calc()` do not require surrounding spaces. They are delim tokens that cannot be confused with number signs, so `calc(100%*2)` and `calc(10px/2)` are valid.

Source

a {
  width: calc(100% * 2);
}

Expected

a{width:calc(100%*2)}

Validate

Outputs

clean-css

a{width:calc(100% * 2)}

csskit

a{width:calc(100% * 2)}

cssnano

a{width:200%}

csso

a{width:calc(100%*2)}

esbuild

a{width:200%}

lightningcss

a{width:200%}
0013
Details

In `transform: rotate(45deg) scale(2)`, the space between `)` and `scale(` can be removed. The `)` token is always its own token and cannot merge with the following function token, so `rotate(45deg)scale(2)` parses identically.

Source

a {
  transform: rotate(45deg) scale(2);
}

Expected

a{transform:rotate(45deg)scale(2)}

Validate

Outputs

clean-css

a{transform:rotate(45deg) scale(2)}

csskit

a{transform:rotate(45deg)scale(2)}

cssnano

a{transform:rotate(45deg) scale(2)}

csso

a{transform:rotate(45deg) scale(2)}

esbuild

a{transform:rotate(45deg) scale(2)}

lightningcss

a{transform:rotate(45deg)scale(2)}
0014
Details

In `background: url(bg.png) no-repeat`, the space between `)` and `no-repeat` can be removed. The `)` token always terminates cleanly and cannot merge with the following ident token, so `url(bg.png)no-repeat` parses identically.

Source

a {
  background: url(bg.png) no-repeat;
}

Expected

a{background:url(bg.png)no-repeat}

Validate

Outputs

clean-css

a{background:url(bg.png) no-repeat}

csskit

a{background:url(bg.png) no-repeat}

cssnano

a{background:url(bg.png) no-repeat}

csso

a{background:url(bg.png)no-repeat}

esbuild

a{background:url(bg.png) no-repeat}

lightningcss

a{background:url(bg.png) no-repeat}
0015
Details

In `font: 16px / 1.5 sans-serif`, the spaces around `/` separating font-size from line-height can be removed. The `/` is a delim token that always stands alone, so `16px/1.5` parses identically. Note the space before `sans-serif` must remain because `1.5sans-serif` would become a single dimension token.

Source

a {
  font: 16px / 1.5 sans-serif;
}

Expected

a{font:16px/1.5 sans-serif}

Validate

Outputs

clean-css

a{font:16px/1.5 sans-serif}

csskit

a{font:16px / 1.5 sans-serif}

cssnano

a{font:16px/1.5 sans-serif}

csso

a{font:16px/1.5 sans-serif}

esbuild

a{font:16px/1.5 sans-serif}

lightningcss

a{font:16px/1.5 sans-serif}
0016
Details

In `div p`, the space IS the descendant combinator. Removing it produces `divp`, a single type selector for an element named `divp` which matches nothing. The space between simple selectors forming a descendant relationship must always be preserved.

Source

div p {
  color: red;
}

Expected

div p{color:red}

Validate

Outputs

clean-css

div p{color:red}

csskit

div p{color:red}

cssnano

div p{color:red}

csso

div p{color:red}

esbuild

div p{color:red}

lightningcss

div p{color:red}
0017
Details

`#` unambiguously starts a hash token in CSS, so no whitespace is needed to separate it from a preceding ident or keyword. `solid #fff` becomes `solid#fff`.

Source

a {
  border: solid #fff;
}

Expected

a{border:solid#fff}

Outputs

clean-css

a{border:solid #fff}

csskit

a{border:solid#fff}

cssnano

a{border:solid #fff}

csso

a{border:solid #fff}

esbuild

a{border:solid #fff}

lightningcss

a{border:solid #fff}
0018
Details

`color: red !important` can drop the space before `!`. The `!` delimiter token is not an ident character so no ambiguity arises when adjacent to the value.

Source

a {
  color: red !important;
}

Expected

a{color:red!important}

Outputs

clean-css

a{color:red!important}

csskit

a{color:red!important}

cssnano

a{color:red!important}

csso

a{color:red!important}

esbuild

a{color:red!important}

lightningcss

a{color:red!important}
0019
Details

Spaces around `+` and `-` operators inside `clamp()` are required just like in `calc()`. Stripping them silently breaks the declaration.

Source

a {
  font-size: clamp(0.5rem, 2vw + 0.5rem, 2rem);
}

Expected

a{font-size:clamp(.5rem,2vw + .5rem,2rem)}

Outputs

clean-css

a{font-size:clamp(.5rem, 2vw + .5rem, 2rem)}

csskit

a{font-size:clamp(.5rem,2vw + .5rem,2rem)}

cssnano

a{font-size:clamp(.5rem,2vw + .5rem,2rem)}

csso

a{font-size:clamp(.5rem,2vw + .5rem,2rem)}

esbuild

a{font-size:clamp(.5rem,2vw + .5rem,2rem)}

lightningcss

a{font-size:clamp(.5rem,2vw + .5rem,2rem)}
Subtotal 14 / 19 15 / 19 15 / 19 17 / 19 13 / 19 15 / 19

zero-units

testclean-csscsskitcssnanocssoesbuildlightningcss
0001
Details

`0px` equals `0` for length properties. The unit is redundant.

Source

a {
  margin: 0px;
}

Expected

a{margin:0}

Outputs

clean-css

a{margin:0}

csskit

a{margin:0}

cssnano

a{margin:0}

csso

a{margin:0}

esbuild

a{margin:0}

lightningcss

a{margin:0}
0002
Details

`0s` must NOT be simplified to `0`. Unitless zero is invalid for `<time>` values. Stripping the unit breaks transitions and animations.

Source

a {
  transition: all 0s;
}

Expected

a{transition:all 0s}

Outputs

clean-css

a{transition:none}

csskit

a{transition:all 0}

cssnano

a{transition:all 0s}

csso

a{transition:all 0s}

esbuild

a{transition:all 0s}

lightningcss

a{transition:all}
0003
Details

`0%` is not always equivalent to `0` (e.g. in `width`). The unit must be preserved.

Source

a {
  width: 0%;
}

Expected

a{width:0%}

Outputs

clean-css

a{width:0%}

csskit

a{width:0%}

cssnano

a{width:0}

csso

a{width:0%}

esbuild

a{width:0%}

lightningcss

a{width:0%}
0004
Details

`padding: 0px 10px 0px 10px` requires both `0px` -> `0` conversion and 4-to-2 shorthand collapse.

Source

a {
  padding: 0px 10px 0px 10px;
}

Expected

a{padding:0 10px}

Outputs

clean-css

a{padding:0 10px}

csskit

a{padding:0 10px 0 10px}

cssnano

a{padding:0 10px}

csso

a{padding:0 10px}

esbuild

a{padding:0 10px}

lightningcss

a{padding:0 10px}
0005
Details

`0.5em` can be shortened to `.5em`.

Source

a {
  margin: 0.5em;
}

Expected

a{margin:.5em}

Outputs

clean-css

a{margin:.5em}

csskit

a{margin:.5em}

cssnano

a{margin:.5em}

csso

a{margin:.5em}

esbuild

a{margin:.5em}

lightningcss

a{margin:.5em}
0006
Details

`1.0` can be shortened to `1`. Trailing fractional zeros are redundant.

Source

a {
  opacity: 1;
}

Expected

a{opacity:1}

Outputs

clean-css

a{opacity:1}

csskit

a{opacity:1}

cssnano

a{opacity:1}

csso

a{opacity:1}

esbuild

a{opacity:1}

lightningcss

a{opacity:1}
0007
Details

`flex: 0 0 0px` shortens to `flex: 0 0`. When flex-grow and flex-shrink are specified as numbers, the omitted flex-basis defaults to `0%`, which is equivalent to `0px` (both resolve to zero length).

Source

a {
  flex: 0 0 0px;
}

Expected

a{flex:0 0}

Outputs

clean-css

a{flex:0 0 0px}

csskit

a{flex:0 0 0}

cssnano

a{flex:0 0 0px}

csso

a{flex:0 0 0px}

esbuild

a{flex:0 0 0px}

lightningcss

a{flex:0 0 0}
0008
Details

`0deg` must NOT be simplified to `0` in the `rotate` property. Unlike legacy transform functions (where browsers accept unitless zero for compatibility), the individual transform properties reject unitless zero per spec. Stripping the unit produces an invalid declaration.

Source

a {
  rotate: 0deg;
}

Expected

a{rotate:0deg}

Outputs

clean-css

a{rotate:0deg}

csskit

a{rotate:0deg}

cssnano

a{rotate:0deg}

csso

a{rotate:0deg}

esbuild

a{rotate:0deg}

lightningcss

a{rotate:none}
0009
Details

In keyframe selectors, `0%` must not be stripped to bare `0`. A `0` without `%` is not valid as a keyframe selector. The `%` unit must be preserved.

Source

@keyframes fade {
  0% {
    opacity: 0;
  }
  50% {
    opacity: 0.5;
  }
}

Expected

@keyframes fade{0%{opacity:0}50%{opacity:.5}}

Validate

Outputs

clean-css

@keyframes fade{0%{opacity:0}50%{opacity:.5}}

csskit

@keyframes fade{0%{opacity:0}50%{opacity:.5}}

cssnano

@keyframes fade{0%{opacity:0}50%{opacity:.5}}

csso

@keyframes fade{0%{opacity:0}50%{opacity:.5}}

esbuild

@keyframes fade{0%{opacity:0}50%{opacity:.5}}

lightningcss

@keyframes fade{0%{opacity:0}50%{opacity:.5}}
0010
Details

Unitless zero is invalid for `<flex>` values. `0fr` in grid track sizing must not be stripped to `0`, which would be parsed as a `<length>`.

Source

a {
  grid-template-columns: 0fr 1fr 2fr;
}

Expected

a{grid-template-columns:0fr 1fr 2fr}

Outputs

clean-css

a{grid-template-columns:0fr 1fr 2fr}

csskit

a{grid-template-columns:0fr 1fr 2fr}

cssnano

a{grid-template-columns:0fr 1fr 2fr}

csso

a{grid-template-columns:0fr 1fr 2fr}

esbuild

a{grid-template-columns:0fr 1fr 2fr}

lightningcss

a{grid-template-columns:0fr 1fr 2fr}
Subtotal 8 / 10 7 / 10 8 / 10 9 / 10 9 / 10 7 / 10