cssminc: Documentation

6 min read Original article ↗

Download instructions

Wha?

For years i've wanted a "minifier" for CSS (Cascading Style Sheets) but all of them i've found are either tens of megabytes huge, with insane amounts of dependencies, and/or are based on node.js (which i despise with all of my cold, cold heart). Well, i'm not getting any younger, and my desire for a CSS minifier is starting to get acute, so i finally sat down for a few hours to Get This Done...

cssminc is a tiny CSS minifier. It's made up of only a small amount of dependency-free, portable, C89-compliant source code. It has only four small files, two C and two headers, and can be built as either a library or a standalone binary. (Building the binary also requires one additional C/header pair with app-level utility code.) The library-only bits compile down to a mere 9kb of binary code.

cssminc is intended to be pronounced "see-ess-ess mincy," "see-ess-ess min-see," "see-ess-ess mink," "see-ess-ess mince," "see-smink," or however else one prefers.

JavaScript and JSON: as of 2026-07-26 this project also hosts jsminc, a JavaScript/JSON counterpart with the same interface except that the configuration options necessarily differ. These docs do not go into detail about that API because it works the same as cssminc does. Unlike cssminc, which was 100% hand-written, jsminc was written by an LLM. It was given cssmin and asked to produce a JS/JSON minifier with the same interface (sans config options, which differ). Its result required only trivial manual tweaking to (A) fix one syntax error (nested /*...*/ comments), (B) squelch warnings from two cases of unused arguments where an interface required a parameter but the concrete implementation does not, and (C) remove an assert() which was carried over verbatim from cssminc but was bogus for JS. It was subsequently hand-refactored to consolidate the common code and interfaces but the JS-specific pieces remain essentially entirely LLM-written.

Features

  • Depending on the distribution, between two and four small, portable, strictly-C89-compatible code with no external dependencies beyond the standard C library. The binary, as opposed to the library, requires an addition two utility files.

  • A minuscule library API which can stream its input and output from/to any source/destination via a simple, abstract I/O interface (one function per direction of the data). The binary reads from stdin and writes to stdout, as any well-behaved Unix-style app should.

  • Full API documentation.

  • Plenty fast and relatively efficient. At any given time it only stores the previous 2 bytes of the input stream and it actively prefers constant-time logic branches (switch/case) over linear-time ones (if/else).

  • The core library has constant memory costs and uses no dynamic memory, but its data source/sink very probably do (opening FILE handles isn't cheap!).

  • Can consume ASCII or UTF-8, including data with "weird characters" in them (so long as they're encoded as UTF-8, not latin-1, some "code page" dialect, or similar).

  • Can optionally try to compress everything, leaving zero (or very close to zero) extraneous space, or it can, for slightly improved readability and the avoidance of ultra-long lines, insert a space or newline after each rule block. (Some tools diagnose files with exceptionally long lines as binary, which may or may not cause usability problems.)

Misfeatures

  • It is not known to work 100% reliably on all valid CSS, but is also not known to not work reliably. Bug reports and patches in that regard would be much appreciated. That said: it is used to minify the CSS for my main site and several sub-sites, so it's known to work reasonably well.

  • It does not "lint" or validate CSS: it assumes that its inputs are already valid CSS (because they were tested for semantic validity before minification, right? Right?).

  • It does not explicitly support any CSS-like dialects such as SASS and LESS or whatever the cool kids are using these days. Projects using such tools already have their favourite node.js-based infrastructure to process it. i have no intentions whatsoever of going down the rabbit hold of trying to support arbitrary non-standard CSS dialects (minor patches to that effect would be gladly accepted, so long as they're not invasive). That said: if a dialect supports the same basic format as CSS, without imposing new syntactic rules, then it "might" work as-is with this code.

License

As of 2026-07-27 it uses the SQLite Blessing "license". Prior to that it had a dual Public Domain/MIT license.

Feedback and patches are happily received: https://wanderinghorse.net/home/stephan/

Examples

The Library API:

cssminc_state cc = cssminc_state_empty; /* similar to memset() but uses some non-0 values */
int rc;
cc.base.in = minc_input_f_FILE; /* input source */
cc.base.inState = stdin; /* state for cc.in */
cc.base.out = minc_output_f_FILE; /* output destination */
cc.base.outState = stdout; /* State for cc.out */
/* cc.options.X = Y see docs */;
rc = cssminc_process(&cc)
  /* 0 on success, non-0 on error, noting that "error" only
     means that it choked on some of the input or one of
     the I/O routines failed. */;

The in and out members can be arbitrary implementations of the I/O abstraction functions, and the inState/outState members are opaque state pointers for use by those functions. e.g. they could be pointers to an app-specific memory buffer type, or the output routine might send its contents directly to a UI widget (nothing that it might do so a byte at a time, so the target needs to be prepared to accept, at least briefly, partial multi-byte characters).

The CLI App

~> time ./cssminc --metrics < big.css > foo.css
./cssminc metrics:
  Input bytes = 260530, output = 137715, = 47.14% reduction
  Output spaces = 4188, non-spaces = 133527

real	0m0.038s
user	0m0.031s
sys	0m0.001s

(That's from a Raspberry Pi 4B system running on an external USB 2.0 hard drive, not a fast workstation.)

Output samples:

Sample (admittedly trivial) input:

$ cat foo.css 
/* This comment is normally retained. */
bläüp, bläüp.x bläüp.y-z,
bläüp.zzz > x
{x:"üö" , yy  :  zz }/* these non-ASCII characters must survive the ccssmin process. */
  /*/ <--- this is a comment, despite
    weird placement of that /. */

/* MOAR comments */

foo { y:z }

         /* Not retained */

bar  { foo:  "barre baz"  }
cssminc -f foo.css -bn

The -bn tells it to add a newline after each closing brace (}):

/* This comment is normally retained. */
bläüp,bläüp.x bläüp.y-z,bläüp.zzz > x{x:"üö",yy:zz}
foo{y:z}
bar{foo:"barre baz"}
cssminc -f foo.css -bs

-bs tells it to add a space after each closing brace:

/* This comment is normally retained. */
bläüp,bläüp.x bläüp.y-z,bläüp.zzz > x{x:"üö",yy:zz} foo{y:z} bar{foo:"barre baz"}
cssminc -f foo.css -b0 -c

-b0 tells it to add nothing after closing braces and -c tells it to strip the initial comment:

bläüp,bläüp.x bläüp.y-z,bläüp.zzz > x{x:"üö",yy:zz}foo{y:z}bar{foo:"barre baz"}

Normally the initial comment of a CSS file contains a license or attribution, and should not be stripped. cssminc only supports keeping the first comment if it is the first non-space content in the input. Comments appearing after non-space content are unconditionally stripped.