# critical

> Extract & inline critical-path CSS — two engines, automatic routing, agent-native.

Latest version **9.0.0** (published 2026-09-13) · Apache-2.0 license · 0 weekly downloads

## Install

```sh
npm install critical
pnpm add critical
yarn add critical
bun add critical
```

Provides the command `critical`.

## Health

**Score 60/100 (C)** — status: active.

Positive: esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types.

## Facts

| | |
|---|---|
| Version | 9.0.0 |
| Published | 2026-09-13 |
| First published | 2014-06-28 |
| Weekly downloads | 0 |
| License | Apache-2.0 |
| TypeScript types | none |
| Module format | ESM |
| Node | >=22.13.0 |
| Dependencies | 3 |
| Unpacked size | 67.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Addy Osmani |
| Maintainers | bezoerb, sindresorhus, addyosmani |
| Keywords | above-the-fold, critical, css, inline, lcp, mcp, performance |

## Links

- npm: https://www.npmjs.com/package/critical
- Repository: addyosmani/critical
- npm.io page: https://npm.io/package/critical

## Dependencies (3)

- [css-tree](https://npm.io/package/css-tree.md) ^3.1.0
- [linkedom](https://npm.io/package/linkedom.md) ^0.18.6
- [lightningcss](https://npm.io/package/lightningcss.md) ^1.30.0

## Alternatives

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

## Recent versions

- 9.0.0 (latest) — 2026-09-13
- 1.3.10 (prev) — 2020-06-16
- 2.0.0-27 (next) — 2020-06-11
- 8.0.0 — 2026-05-17
- 7.2.1 — 2024-09-23
- 7.2.0 — 2024-08-17
- 7.1.2 — 2024-04-21
- 7.1.1 — 2024-03-26
- 7.1.0 — 2024-01-28
- 7.0.0 — 2024-01-27
- 6.0.5 — 2024-01-11
- 6.0.4 — 2023-10-09
- 6.0.3 — 2023-10-08
- 6.0.2 — 2023-10-08
- 6.0.1 — 2023-10-06
- … 98 more at https://npm.io/package/critical/versions

## README

# critical

> Extract the critical-path (above-the-fold) CSS from your HTML, inline it, and load the rest
> asynchronously — so the browser can paint the first screen without waiting on a full stylesheet.

Critical removes render-blocking CSS from the critical path, which is one of the most direct
levers on **Largest Contentful Paint** and first render. It works for static sites, MPAs, and
single-page apps, and it's built to be driven by humans, build pipelines, and coding agents
alike.

```sh
npm install --save-dev critical
```

---

## Contents

- [How it works](#how-it-works)
- [Quick start](#quick-start)
- [Two engines](#two-engines)
- [CLI reference](#cli-reference)
- [API reference](#api-reference)
- [Inlining behavior](#inlining-behavior)
- [Tuning the above-the-fold set](#tuning-the-above-the-fold-set)
- [MCP server (agents)](#mcp-server-agents)
- [Recipes](#recipes)
- [Design principles](#design-principles)
- [Requirements](#requirements)
- [Alternatives](#alternatives)

---

## How it works

Given an HTML document and its stylesheets, Critical:

1. **Determines what's critical.** It figures out which CSS rules style the content that paints
   above the fold — either by matching rules against the delivered DOM (fast, no browser) or by
   rendering the page in a real viewport (accurate, handles SPAs). See [Two engines](#two-engines).
2. **Inlines it.** The critical rules go into a `<style>` at the top of `<head>` so first paint
   needs only the HTML.
3. **Defers the rest.** Each `<link rel="stylesheet">` is replaced in the head by a
   `<link rel="preload">` and moved to the end of the body, so it no longer blocks first paint.
   No inline scripts are added, so it works under a strict Content Security Policy.

The output is deterministic: the same input produces byte-identical output, so it's safe to run
in CI and diff in version control.

## Quick start

### CLI

```sh
# Optimize a build directory in place (inlines critical CSS, defers the rest)
critical ./dist --inline --write

# See what it would do and why, without writing anything
critical ./dist --explain

# A single file to stdout
critical index.html --inline > index.critical.html
```

### API

```js
import { critical } from "critical";

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

// `html`   — the document with critical CSS inlined and stylesheets deferred
// `css`    — the critical CSS on its own (minified)
// `report` — structured diagnostics (engine used, bytes, rules, warnings, timing)
```

`critical()` never writes to disk on its own — it returns the result. Use the CLI's `--write`
/ `--out`, or write `result.html` / `result.css` yourself.

### Docker

The container image includes Playwright and Chromium, so both the static and render engines work
without additional setup. Mount the generated site at `/site` and include `critical` before its
arguments:

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

## Two engines

Critical picks the right strategy for each document automatically (`engine: "auto"`, the
default), and tells you which it used and why.

| Engine     | How it decides what's critical                                               | Needs a browser  | Best for                                                     |
| ---------- | ---------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------ |
| **static** | Matches CSS rules against the delivered DOM (the used CSS)                   | No               | SSG / SSR / MPA output that ships real markup                |
| **render** | Loads the page at a real viewport and measures what's painted above the fold | Yes (Playwright) | SPA shells, and when you want a tight, viewport-accurate set |

**Automatic routing.** When `engine` is `"auto"`, Critical inspects the delivered HTML. If the
document already contains rendered content, the static engine is correct and runs in
milliseconds. If the document is an empty application shell (e.g. `<div id="root"></div>` with
no markup yet), there's nothing to match against statically, so Critical escalates to the render
engine and measures the page the way a browser actually paints it.

You can always pin the engine explicitly with `engine: "static"` or `engine: "render"`.

> The render engine uses [Playwright](https://playwright.dev), declared as an **optional** peer
> dependency. It's imported lazily, so the static path — and the default install — never pull in
> a browser. To use the render engine: `npm i -D playwright && npx playwright install chromium`.

## CLI reference

```text
critical <input> [options]

  input        a directory (all *.html are processed), an .html file, or stdin
```

| Option                                | Description                                                | Default |
| ------------------------------------- | ---------------------------------------------------------- | ------- |
| `-e, --engine <auto\|static\|render>` | Engine selection                                           | `auto`  |
| `-i, --inline`                        | Inline critical CSS and defer the rest                     | off     |
| `-w, --width <px>`                    | Render-engine viewport width                               | `1300`  |
| `-h, --height <px>`                   | Render-engine viewport height                              | `900`   |
| `--dimensions <WxH,WxH>`              | Multiple render viewports (e.g. `390x844,1300x900`)        | —       |
| `--no-fold`                           | Ignore `[data-critical-fold]` scoping in the static engine | —       |
| `--no-minify`                         | Keep the critical CSS readable instead of minifying        | —       |
| `-o, --out <file\|dir>`               | Write output here instead of stdout                        | stdout  |
| `--write`                             | Rewrite the input file(s) in place (use with `--inline`)   | off     |
| `--json`                              | Emit the structured result as JSON                         | off     |
| `--explain`                           | Print the engine decision and stats to stderr              | off     |
| `--help`                              | Show help                                                  | —       |

### Examples

```sh
critical ./dist --inline --write              # whole build dir, in place
critical index.html --explain                 # routing decision + size stats
critical app.html -e render -i > out.html     # force a real-browser pass for an SPA
critical page.html --dimensions 390x844,1300x900 -i   # union of mobile + desktop folds
cat page.html | critical --inline             # stdin -> stdout
critical ./dist --json                        # machine-readable report for CI/agents
```

## API reference

```js
import { critical } from "critical";
```

### `critical(options) → Promise<{ html, css, report }>`

| Option       | Type                             | Default                | Description                                                                             |
| ------------ | -------------------------------- | ---------------------- | --------------------------------------------------------------------------------------- |
| `src`        | `string`                         | —                      | Path or URL to an HTML file. Provide `src` **or** `html`.                               |
| `html`       | `string`                         | —                      | Raw HTML source. Takes precedence over `src`.                                           |
| `css`        | `string \| string[]`             | —                      | Extra CSS: file paths, globs, or raw CSS strings, beyond what the document links.       |
| `base`       | `string`                         | dir of `src`, else cwd | Base directory for resolving stylesheet/asset paths.                                    |
| `engine`     | `"auto" \| "static" \| "render"` | `"auto"`               | Engine selection (see [Two engines](#two-engines)).                                     |
| `inline`     | `boolean \| object`              | `false`                | Inline critical CSS and defer the rest. Pass an object to configure inlining.           |
| `minify`     | `boolean`                        | `true`                 | Minify the critical CSS (via Lightning CSS).                                            |
| `foldAware`  | `boolean`                        | `true`                 | Honor `[data-critical-fold]` scoping in the static engine.                              |
| `width`      | `number`                         | `1300`                 | Render-engine viewport width.                                                           |
| `height`     | `number`                         | `900`                  | Render-engine viewport height.                                                          |
| `dimensions` | `Array<{width, height}>`         | —                      | Multiple render viewports; their critical sets are unioned. Overrides `width`/`height`. |
| `timeout`    | `number`                         | `30000`                | Render-engine navigation timeout (ms).                                                  |
| `userAgent`  | `string`                         | —                      | User agent for the render engine.                                                       |

The `inline` object accepts:

| Key       | Type      | Default | Description                                                                    |
| --------- | --------- | ------- | ------------------------------------------------------------------------------ |
| `preload` | `boolean` | `true`  | Insert a `<link rel="preload" as="style">` hint where each deferred sheet was. |
| `nonce`   | `string`  | —       | Nonce to set on the injected `<style>`, for a strict `style-src` CSP.          |

### The result

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

- **`html`** — the document. Identical to the input unless `inline` is set.
- **`css`** — the critical CSS, minified (unless `minify: false`).
- **`report`** — structured diagnostics:

```jsonc
{
  "engine": "static", // engine that actually ran
  "reason": "rendered document (…) — matching used CSS without a browser",
  "requestedEngine": "auto",
  "rules": { "kept": 10, "total": 15 },
  "bytes": { "stylesheets": 1107, "critical": 544, "savedBlocking": 1107 },
  "stylesheetsDiscovered": ["/styles.css"],
  "stylesheetsDeferred": ["/styles.css"], // present when inlined
  "warnings": ["…"],
  "durationMs": 13,
  "deterministic": true,
}
```

`report` is designed to be read by a program: it explains the decision, quantifies the win
(`savedBlocking` = render-blocking bytes removed from the critical path), and surfaces any
caveats as `warnings`.

## Inlining behavior

With `inline: true`, Critical:

1. Inserts `<style data-critical>…</style>` as the first child of `<head>` so it wins the
   cascade race against the deferred sheets.
2. Stops each `<link rel="stylesheet">` from blocking the first paint, using the preload strategy:

   ```html
   <!-- in the head, where the stylesheet was: -->
   <link rel="preload" as="style" href="/styles.css" />

   <!-- moved to the end of <body>: -->
   <link rel="stylesheet" href="/styles.css" />
   ```

   The preload starts the download early; the stylesheet applies after the above-the-fold content
   (already styled by the inlined critical CSS) has painted. There is **no inline event handler**,
   so this works under a strict `script-src` Content Security Policy, and **no `<noscript>`
   fallback is needed** — the stylesheet loads normally whether or not JavaScript runs.

Add `data-critical-skip` to a `<link>` to leave it untouched. For a strict `style-src` CSP, pass
`inline: { nonce: "…" }` to stamp the injected `<style>` with your nonce.

## Tuning the above-the-fold set

- **`[data-critical-fold]` (static engine).** By default the static engine inlines all _used_
  CSS. Mark the container that holds your above-the-fold content with `data-critical-fold` and
  the static engine scopes matching to that subtree, producing a tighter critical set without a
  browser.
- **Viewports (render engine).** Use `width`/`height` or `dimensions` to define exactly what
  "above the fold" means. Multiple dimensions union their results — useful for shipping one
  critical set that covers both mobile and desktop.
- **`@font-face`, `@keyframes`, custom properties** are preserved when referenced; unused
  `@keyframes` and empty `@media`/`@supports`/`@layer` blocks are pruned.

## MCP server (agents)

Critical ships a [Model Context Protocol](https://modelcontextprotocol.io) server so an agent
can call it as a tool and receive the structured report back directly:

```sh
node src/mcp.js     # stdio MCP server exposing `optimize_critical_css`
```

```js
import { createServer } from "critical/mcp";
```

The tool takes `src`/`html` (plus optional `css`, `engine`, `inline`, `width`, `height`) and
returns the critical CSS, the rewritten HTML, and the report as structured content. Requires the
optional `@modelcontextprotocol/sdk` peer dependency.

## Recipes

**Optimize a static build directory (in CI):**

```sh
critical ./dist --inline --write
```

**Programmatically, writing both outputs:**

```js
import { writeFile } from "node:fs/promises";
import { critical } from "critical";

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

**An SPA where the shell is empty until JS runs:**

```js
await critical({ src: "dist/index.html", engine: "render", inline: true });
// auto-routing would also pick "render" here; pinning it just skips the sniff.
```

## Design principles

- **Two engines, automatically routed.** The fast path runs whenever the delivered DOM is the
  source of truth; the browser is used only when it's actually needed.
- **Platform-first, few dependencies.** Three runtime dependencies — `lightningcss` (parse +
  minify + dead-code), `linkedom` (DOM), `css-tree` (rule walking). Everything else is a Node
  built-in (`fetch`, `parseArgs`, `styleText`, `fs.glob`). Heavy capabilities (`playwright`, the
  MCP SDK) are optional peers, imported lazily — you only pay for what you use.
- **Deterministic and explainable.** Stable output bytes and a structured `report` that says
  what ran, why, and what it saved.
- **Safe by default.** Nothing is written without an explicit flag; deferral adds no inline
  scripts, so the output works under a strict Content Security Policy.

## Requirements

- **Node.js ≥ 22.13**
- The **render engine** additionally needs `playwright` and a browser:
  `npm i -D playwright && npx playwright install chromium`. The static engine needs neither.

## Alternatives

Critical isn't the only way to inline critical CSS, and it isn't always the right one — a purely
prerendered site that just wants its used CSS inlined may be better served by a lighter,
browser-free tool. For a thorough, fair comparison of Critical, Beasties (formerly Critters),
Penthouse, and native platform approaches — including where each one is the better choice and a
reproducible benchmark — see [COMPARISON.md](./COMPARISON.md).

## Maintainers

This module is brought to you and maintained by the following people:

- Addy Osmani - Creator ([Github](https://github.com/addyosmani))
- Ben Zörb - Primary maintainer ([Github](https://github.com/bezoerb))

## License

[Apache-2.0](license) © Addy Osmani

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