CLI tool
addyosmani/critical avatar
addyosmani/critical

addyosmani/critical: inlining above-the-fold CSS with two engines

Extract & Inline Critical-path CSS in HTML pages

10,284 stars390 forksJavaScriptApache-2.0

At a glance

What is it?
Critical extracts the CSS that styles the first screen, inlines it in the head, and defers the rest. Version 9.0.0 adds automatic routing between a DOM-matching static engine and a Playwright render engine.
Who is it for?
Adopt addyosmani/critical when you ship real markup from an SSG, SSR or MPA build and want the render-blocking stylesheet out of the first paint without adding inline scripts. Skip it for SPA shells unless you are willing to install Playwright plus Chromium and pay for a real browser pass, and skip it entirely if your pipeline cannot run Node 22.13 or newer.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The render-blocking stylesheet problem Critical targets

A browser that meets a <link rel="stylesheet"> in the head will not paint until that stylesheet has been fetched and parsed. On a large site the full sheet is mostly rules for pages, components and breakpoints the visitor has not reached yet, and those rules sit between the request and the first pixel. Critical attacks exactly that gap. It takes an HTML document plus its stylesheets, works out which rules style the content that paints above the fold, moves those rules into a <style> block at the top of the head, and turns each remaining stylesheet link into a preload that is moved to the end of the body.

The README frames this as one of the most direct levers on Largest Contentful Paint and first render, and that is the right frame: the tool does not compress images or shrink JavaScript, it removes a network dependency from the critical path. The audience is anyone producing static HTML, whether from a static site generator, a server-rendered app, or a multi-page site, plus the build pipelines and coding agents that run on their behalf. If your pages are rendered entirely by client-side JavaScript with no server markup, read the engine section below before assuming this fits.

How Critical decides what is critical: two engines and automatic routing

The mechanism has two implementations. The static engine matches CSS rules against the delivered DOM, which the README calls the used CSS. It needs no browser and runs in milliseconds, and it is correct when the HTML that arrives already contains the content a visitor sees. The render engine loads the page at a real viewport and measures what is actually painted above the fold. It needs Playwright and Chromium, and it is the accurate path for SPA shells and for anyone who wants a viewport-tight set.

With the default engine: "auto", Critical inspects the delivered HTML and routes. A document that already contains rendered content goes to the static engine. A document that is an empty application shell, the README's example is a div with id root and no markup, has nothing to match against statically, so Critical escalates to the render engine. You can pin either path with engine: "static" or engine: "render". The engine choice is reported, and --explain prints the decision and stats to stderr, which is the fastest way to find out which path your build actually took.

Playwright is an optional peer dependency and is imported lazily, so the default install never pulls in a browser. That is a deliberate split: the cheap path stays cheap, and the expensive path is opt-in. The viewport for the render engine defaults to 1300 by 900, and --dimensions accepts a comma-separated list such as 390x844,1300x900 to take the union of a mobile and a desktop fold. The inlining step adds no inline scripts, which the README notes means the output works under a strict Content Security Policy. Output is deterministic: the same input produces byte-identical output, which is what makes it safe to run in CI and diff in version control.

Installing Critical and a first real run

The package installs as a dev dependency. The README gives this command:

bash
npm install --save-dev critical

Node 22.13.0 or newer is required, according to the engines field in package.json, and the project itself uses pnpm 11.5.0 as its package manager. The CLI binary is named critical. The quickest way to see what the tool would do before it changes anything is the explain mode, which writes the routing decision and size stats to stderr and does not touch your files:

bash
critical ./dist --explain

The input can be a directory, in which case every *.html file is processed, a single .html file, or stdin. When you are ready to change the build output, inline the critical CSS and rewrite the files in place:

bash
critical ./dist --inline --write

A single file to stdout is the other common shape, useful when you want to inspect the result before trusting it:

bash
critical index.html --inline > index.critical.html

From Node, the API returns rather than writes. The README shows the shape: call critical with src and inline, and you get back html with the critical CSS inlined and the stylesheets deferred, css holding the critical CSS on its own and minified, and report holding structured diagnostics including the engine used, bytes, rules, warnings and timing. Nothing is written to disk unless you use the CLI's --write or --out.

js
import { critical } from "critical";

const { html, css, report } = await critical({
  src: "dist/index.html",
  inline: true,
});

If you would rather not manage Chromium yourself, the repository ships a Dockerfile built on the official Playwright image, which already contains Chromium and its system dependencies. The README's example mounts the generated site at /site and passes critical before its arguments:

bash
docker run --rm -v "$PWD/dist:/site" ghcr.io/addyosmani/critical:master \
  critical . --inline --write

One detail worth knowing from the Dockerfile: the image builds from the committed source with npm pack rather than pulling the published package, so the container matches the repository state rather than whatever is on the registry.

Where Critical is the wrong tool

The static engine only knows what is in the delivered HTML. If your application renders its content in the browser, the static pass has almost nothing to match against, and the automatic routing will push you to the render engine. That path is not free: it needs Playwright and Chromium installed, and every document gets a real page load. For a build directory with hundreds of pages, that is a different cost profile from a millisecond DOM match, and the README does not offer a caching layer or an incremental mode to soften it.

The viewport is a second boundary. Above-the-fold is a geometric claim, and the render engine defaults to 1300 by 900. A visitor on a phone sees a different fold, and the --dimensions flag exists precisely because one viewport is not the whole story. The union of several viewports produces a larger critical set, which means more bytes inlined into the head of every page. There is a real tension here between accuracy and payload, and the tool leaves the choice to you.

Finally, the inlining step assumes the tool can rewrite your stylesheet links. Pages that construct their stylesheets at runtime, or that depend on link elements being in a particular position in the head, are a poor fit. The README also does not document rollback, so the safety net is your own version control.

Critical compared with beasties

The repository ships a benchmark script, bench/vs-beasties.mjs, and lists beasties as a dev dependency, which tells you the comparison the maintainers care about. The difference is in the approach rather than the output shape. Beasties is the static-only lineage: it matches rules against the delivered DOM and never opens a browser. Critical keeps that path as its default for documents that already contain markup, and adds the render engine for the case where there is nothing to match. So for a static site generator producing real HTML, the two are solving the problem the same way and the static engine in Critical is the comparable path. For an SPA shell, beasties has no answer and Critical escalates.

That is a genuine capability difference, not a marketing one, and it comes with the cost described above: the render path drags in Playwright. The other comparison the README points at is the manual alternative, extracting the above-the-fold CSS by hand or with a one-off script, which does not survive a redesign. Critical's answer to that is determinism and the --explain report, which let you put the step in CI and see when the engine decision changes.

Licence, maintenance and the cost of upgrading

The package is Apache-2.0, which is a permissive licence with an explicit patent grant and a requirement to preserve notices. That is a different posture from MIT in the patent clause and the notice handling, and if your organisation has a policy on which permissive licences it accepts, Apache-2.0 is the identifier to check rather than the one you may be assuming. This is a description of the licence file, not legal advice; the license file at the repository root is the authority.

The last push to the default branch was on 2026-09-13, the same day v9.0.0 was released, and the version before that, v8.0.0, landed on 2026-05-17. The gap before v8.0.0 was longer: v7.2.1 is dated 2024-09-23. So the project has a history of long quiet stretches punctuated by major releases, and v9.0.0 is a major version bump. Major bumps in a tool that rewrites your build output are the ones to read the changelog for, since the CHANGELOG.md sits at the repository root for exactly that purpose. Upgrading also carries an environment cost: Node 22.13.0 or newer. If your CI image is pinned below that, the upgrade is a Node upgrade first and a Critical upgrade second.

Editorial conclusion

Adopt addyosmani/critical when you ship real markup from an SSG, SSR or MPA build and want the render-blocking stylesheet out of the first paint without adding inline scripts. Skip it for SPA shells unless you are willing to install Playwright plus Chromium and pay for a real browser pass, and skip it entirely if your pipeline cannot run Node 22.13 or newer. Before you commit, run critical ./dist --explain and read the engine decision and byte counts, then diff the rewritten files in version control to confirm the deterministic output claim holds for your templates.

Frequently asked questions

How do I install addyosmani/critical?

Install it as a dev dependency with npm install --save-dev critical. The package requires Node 22.13.0 or newer, and the CLI binary is named critical. The repository also publishes a Docker image based on the official Playwright image if you want Chromium included.

Does addyosmani/critical need a browser to run?

Not for the static engine, which matches CSS rules against the delivered DOM and needs no browser. The render engine uses Playwright, declared as an optional peer dependency and imported lazily, so the default install does not pull in a browser. To use it you install Playwright and Chromium yourself.

How does addyosmani/critical choose between the static and render engines?

With the default engine setting of auto, it inspects the delivered HTML. A document that already contains rendered content goes to the static engine; an empty application shell has nothing to match against, so Critical escalates to the render engine. You can pin either one explicitly.

How do I check what addyosmani/critical would change before it writes anything?

Run critical ./dist --explain, which prints the engine decision and size stats to stderr without writing. The API also returns a report object with the engine used, bytes, rules, warnings and timing, and critical() never writes to disk on its own.

Official sources

  1. addyosmani/critical on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/addyosmani-critical.svg)](https://hysenlabs.com/projects/addyosmani-critical)