critical Documentation

Extract & Inline Critical-path CSS in HTML pages

10,180 stars JavaScript JavaScript 1 file ยท ~11,684 tokens #critical-css#critical-path-css#critical-path-styles#css#inline-css#inline-styles
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy critical on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name critical --restart always addyosmani/critical:latest

1. Project Overview & Quickstart (addyosmani/critical)

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

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

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. `` 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, 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  [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 Render-engine viewport width 1300
-h, --height 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).
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
    <link rel="preload" as="style" href="/styles.css" />
    
    
    <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 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.

License

Apache-2.0 ยฉ Addy Osmani

2. Official Technical Reference & Guides (addyosmani/docs)

File: README.md

Polymer docs are mostly in Markdown with some HTML. Jekyll is used to generate the static HTML for the site. The output is generated into a folder called _site and served from Google App Engine.

Prereqs and installation requirements

We use Jekyll 1.4.2+ and Grunt to generate the documentation. You'll need to install the requirements before working on the docs (these instructions assume NPM is already installed):

text
sudo gem install jekyll
sudo gem install kramdown
sudo npm install grunt-cli
npm install

You'll also need the App Engine SDK to run the dev_appserver and preview the docs locally. Download the SDK.

Check out the documentation

Checkout this repo:

text
git clone https://github.com/Polymer/docs.git --recursive

Create a directory polymer-all/projects and run the pull-all-projects.sh script in that directory:

text
cd docs/polymer-all
mkdir projects; cd projects
../tools/bin/pull-all-projects.sh

This populates projects with a bunch of repositories. You'll periodically need to re-run pull-all-projects.sh whenever the site is released.

Next, create a components folder in the top level docs directory and run pull-all-polymer.sh and pull-all-elements.sh in it:

text
cd ../..
mkdir components; cd components
../polymer-all/tools/bin/pull-all-polymer.sh
../polymer-all/tools/bin/pull-all-elements.sh

Note: these scripts can take some time to download.

You should now be able to build the docs successfully. Run this in the main folder:

text
grunt docs

Making edits and previewing changes

This repo (Polymer/docs) is where the documentation source files live. To make a change:

  1. First, up the App Engine dev server in this folder (dev_appserver.py .) to preview the docs.
  2. Make your edits.
  3. To build the docs, run grunt in base of the docs diretory you checked out. This starts up jekyll and watches for changes as you make edits. Be sure to run npm install in your docs directory if it's a new checkout. It can take some time for the docs to fully regenerate and be copied to the output folder...keep refreshing!

Jekyll generates the static site in a folder named _site. Note: If you're not running jekyll to rebuild the site, you won't see your changes in the dev server.

Once your changes look good, git commit them and push.

Building and pushing the docs

Note: only project owners can publish the documentation.

If you have things checked out correctly in components and polymer-all, you should be able to generate the prodcution documentation using:

text
grunt docs

It's a good idea to do this before pushing the docs, as it runs a number of grunt tasks. Verify things went well.

Last step is to push the docs to App Engine. In your Polymer/docs directory, run:

text
./scripts/deploy_site.sh

This script builds the site, api docs, runs Vulcanizer over the imports, and deploys to App Engine.

Polymer release

When we push a new version of Polymer, the site should be updated to use it. In addition,
the element reference and other projects will need updating.

First, update the submodules in /docs:

text
git submodule foreach git pull origin master
git submodule update

Update the projects:

text
cd polymer-all/projects
../tools/bin/pull-all-projects.sh

Then, update the elements and polyfills:

text
cd components
../polymer-all/tools/bin/pull-all-polymer.sh
../polymer-all/tools/bin/pull-all-elements.sh

Once these are updated, you need to update some versions for the docs:

  • Increment the version in app.yaml;
  • Update the Polymer release version in _config.yml.
  • Add a link point link to the release notes in changelog.md.

Build the docs:

text
grunt docs

At this point, run the dev server and preview things locally to make sure nothing is terribly
broken after Polymer and the elements have been updated.

Lastly, run the deploy script:

text
./scripts/deploy_site.sh

Last thing is to switch the app version in the App Engine admin console. To make the docs live, hit up https://appengine.google.com/deployment?&app_id=s~polymer-project and select the version you just deployed.


File: doc_themes/footstrap/README.md

yuidoc-bootstrap-theme

A revamped yuidoc theme with bootstrap


File: doc_themes/bootstrap/README.md

yuidoc-bootstrap-theme

A revamped yuidoc theme with bootstrap


File: docs/polymer/databinding.md


layout: default title: Data-binding

Introduction

{{site.project_title}} supports two-way data binding and uses the term to describe a way to write dynamic HTML using HTML. A set of platform technologies and "prollyfills" enable this behavior:

When used together, these primitives extend HTML and the DOM APIs to support a sensible
separation between the UI (DOM) of application and its underlying data (model). Updates to the model are reflected in the DOM and user input into the DOM is immediately assigned to the model.

The basics

{{site.project_title}}'s data-binding features hinge on the <template> element and double mustache {%raw%}{{}}{%endraw%} syntax.

An example

There's plenty of detail, but it all hinges on the <template> element. Let's walk through a simple example which demonstrates the basics.

{%raw%}

text
<template>
    
      <template id="greeting" repeat="{{s in salutations}}">
        <li>{{s.what}}: </li>
      </template>
    
  </template>
  <script>
    Polymer('greeting-tag', {
      ready: function() {
        this.salutations = [
          {what: 'Hello', who: 'World'},
          {what: 'GoodBye', who: 'DOM APIs'},
          {what: 'Hello', who: 'Declarative'},
          {what: 'GoodBye', who: 'Imperative'}
        ];
      }
    });
  </script>

{%endraw%}

{%comment%}

text
<template id="greeting" repeat="{{ salutations }}">
    <li>{{ what }}: </li>
  </template>

<script>
var t = document.getElementById('greeting');
var model = {
  salutations: [
    { what: 'Hello', who: 'World' },
    { what: 'GoodBye', who: 'DOM APIs' },
    { what: 'Hello', who: 'Declarative' },
    { what: 'GoodBye', who: 'Imperative' }
  ]
};
t.model = model;

// Needed to detect model changes if Object.observe() is not available.
Platform.performMicrotaskCheckpoint();
</script>

{%endcomment%}

This example should look mostly familiar to anyone who knows HTML, but there are a couple novel things going on:

The `