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.
npm install --save-dev criticalContents
- How it works
- Quick start
- Two engines
- CLI reference
- API reference
- Inlining behavior
- Tuning the above-the-fold set
- MCP server (agents)
- Recipes
- Design principles
- Requirements
- Alternatives
How it works
Given an HTML document and its stylesheets, Critical:
- 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. - Inlines it. The critical rules go into a
<style>at the top of<head>so first paint
needs only the HTML. - 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
# 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.htmlAPI
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
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
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/agentsAPI reference
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
const { html, css, report } = await critical({ src: "dist/index.html", inline: true });htmlโ the document. Identical to the input unlessinlineis set.cssโ the critical CSS, minified (unlessminify: false).reportโ structured diagnostics:
{
"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:
Inserts
<style data-critical>โฆ</style>as the first child of<head>so it wins the
cascade race against the deferred sheets.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 strictscript-srcContent 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, passinline: { 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 withdata-critical-foldand
the static engine scopes matching to that subtree, producing a tighter critical set without a
browser.- Viewports (render engine). Use
width/heightordimensionsto 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@keyframesand empty@media/@supports/@layerblocks 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:
node src/mcp.js # stdio MCP server exposing `optimize_critical_css`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):
critical ./dist --inline --writeProgrammatically, writing both outputs:
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:
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
reportthat 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
playwrightand 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):
sudo gem install jekyll
sudo gem install kramdown
sudo npm install grunt-cli
npm installYou'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:
git clone https://github.com/Polymer/docs.git --recursiveCreate a directory polymer-all/projects and run the pull-all-projects.sh script in that directory:
cd docs/polymer-all
mkdir projects; cd projects
../tools/bin/pull-all-projects.shThis 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:
cd ../..
mkdir components; cd components
../polymer-all/tools/bin/pull-all-polymer.sh
../polymer-all/tools/bin/pull-all-elements.shNote: these scripts can take some time to download.
You should now be able to build the docs successfully. Run this in the main folder:
grunt docsMaking edits and previewing changes
This repo (Polymer/docs) is where the documentation source files live. To make a change:
- First, up the App Engine dev server in this folder (
dev_appserver.py .) to preview the docs. - Make your edits.
- To build the docs, run
gruntin base of the docs diretory you checked out. This starts up jekyll and watches for changes as you make edits. Be sure to runnpm installin 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:
grunt docsIt'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:
./scripts/deploy_site.shThis 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:
git submodule foreach git pull origin master
git submodule updateUpdate the projects:
cd polymer-all/projects
../tools/bin/pull-all-projects.shThen, update the elements and polyfills:
cd components
../polymer-all/tools/bin/pull-all-polymer.sh
../polymer-all/tools/bin/pull-all-elements.shOnce 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:
grunt docsAt 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:
./scripts/deploy_site.shLast 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:
- HTML Template element (
<template>) - Template Bindings (prollyfill) - describes how
<template>manages instance fragments. Node.bind()(prollyfill) - describes how DOM nodes are bound to data values.Object.observe()- Mutation Observers
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%}
<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%}
<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 `` element
The HTML Template element allows you to declare chunks of inert HTML that may be cloned, activated, and used at some laster point.
If you loaded the above example without <script src="platform.min.js"></script>, that's about all <template> would do.
However, Template Binding teaches <template> some new tricks:
- Instruct DOM nodes to derive their value from JavaScript data by binding them to the data provided.
- Maintain a fragment of DOM (or "instance fragment") for each item in an array.
- Conditionally stamp out one or more instance fragments, based on whether some data value is true or not.
- ...And lots more.
But back to the example. Our template...
{%raw%}
{%endraw%}
...defines what each instance will look like when stamped out. In this case, it contains a <li> with a text node and an `` as its children. The mustaches {%raw%}{{}}{%endraw%} mean "bind data here". The {%raw%}repeat="{{ salutations }}"{%endraw%} tells the template to ensure there is one instance fragment for each element in the salutations array.
In <script>, we create a model:
var model = {
salutations: [
{ what: 'Hello', who: 'World' },
{ what: 'GoodBye', who: 'DOM APIs' },
{ what: 'Hello', who: 'Declarative' },
{ what: 'GoodBye', who: 'Imperative' }
]
};Notice that this is just JavaScript data: thereโs no need to import your data into special observable objects. The template is set in motion by binding the model data to it:
t.model = model;Now the template is off to the races. Here's the result:
and here's what the DOM looks like:
You can see that the template stamped out four instances immediately following its position in the document. All nodes within an instance have a property called templateInstance which points to an instance descriptor. The descriptor indicates the extent (first and last nodes) of the instance, as well as the model data for which the instance was produced:
Now, remember we said data-binding teaches the DOM to derive its values from JavaScript data? If we change a value in our model, the DOM observes the change and updates accordingly:
However, the DOM doesnโt just observe data in the model, if DOM elements which collect user input are bound, they push the collected value into the model:
Lastly, let's look at what happens when we alter the contents of the model.salutations array:
The <template> is repeating which means that it ensures there is one instance for each item in the array. We removed two elements from the middle of salutations and inserted one in their place. The <template> responded by removing the two corresponding instances and creating a new one in the right location.
Getting the idea? Data-binding allows you author your HTML using HTML which contains information about where data goes and directives which control the document's structure -- all depending on the data you provide it.
Where to go from here?
If you are new to data-binding, the best to place to go is to the look at the How-To examples. These are little examples which succinctly demonstrate how to use data-binding to accomplish things that frequently are required for real web apps:
Binding to DOM values:
- Binding to text values: How to insert values into the DOM that render as text.
- Binding to attributes: How to insert values into element attributes
- Conditional attributes: How to bind to attributes such that the attribute is only present if the binding value is โtruthyโ.
- Binding to input elements: How to bind bi-directionally with input elements.
- Custom bindings: How to implement a custom element which has a specialized interpretation of a binding.
Using <template> to produce DOM structures:
- Conditionals: How to control whether instance fragments are produced based on the value of a binding.
- Nested templates: How to accomplish nested template production.
- Re-using templates: How to define a template once and use it in more than one location.
- Recursive templates: How to produce tree-structure DOM whose depth is dependent on the data to which it is bound.
Advanced Topics
IMPORTANT: The advanced topics documentation have yet to be created.
- DOM Stability: {{site.project_title}} makes every effort to maintain the state of DOM nodes (event listeners, expandos, etc...). Understand why this is important and how it works.
- Imperative DOM mutation: You should rarely need to directly manipulate the DOM, but if you do, it's allowed. Learn the simple rules of how {{site.project_title}} will react if you manipulate the DOM it is managing.
- Asynchronous processing model: {{site.project_title}} responds asynchronously to changes in data and DOM. Learn why this is good and what it means for your application.
- Chained observation
File: docs/polymer/expressions.md
layout: default title: Expressions
{% include toc.html %}
Within a `` you can use {{site.project_title}}'s expression library to write inline expressions, statements, named scopes, and iterative markup, anywhere {%raw%}{{}}{%endraw%} bindings are used.
Inline expressions
{{site.project_title}} supports expressions in {%raw%}{{}}{%endraw%} with a strict
subset of the JavaScript language. In order to use this feature, it's
important to understand its behavior and limitations:
- The goal for inline expressions is to allow the expression of simple value
concepts and relationships. It is generally bad practice to put complex logic
into your HTML (view). - Expressions are never run (e.g.
eval) as page script. They cannot access any
global state (e.g.window). They are parsed and converted to a simple
interpreted form which is provided the present values of paths contained in
the expression.
The specific subset of JavaScript which is supported is:
| Feature | Example | Explanation
|---------|
|Identifiers & paths | foo, foo.bar.baz | These values are treated as relative to the local model, extracted, observed for changes and cause the expression to be re-evaluated if one or more has changed.
| Logical not operator | ! |
| Unary operators | +foo, -bar | Converted to Number. Or converted to Number, then negated.
| Binary operators | foo + bar, foo - bar, foo * bar | Supported: +, -, *, /, %
| Comparators | foo < bar, foo != bar, foo == bar | Supported: <, >, <=, >=, ==, !=, ===, !==
| Logical comparators | foo && bar || baz | Supported: ||, &&
| Ternary operator | a ? b : c |
| Grouping (parenthesis) | (a + b) * (c + d) |
| Literal values | numbers, strings, null, undefined | Escaped strings and non-decimal numbers are not supported. |
| Array & Object initializers | [foo, 1], {id: 1, foo: bar} |
{: .table }
Statements
Expressions are parsed when they're within a mustache ({% raw %}{{}}{% endraw %}).
The expression can be a single statement, or an object.
Whenever the value of one or more paths in the expression change, the value of the expression re-evaluated and the result inserted as the value of the mustache:
{% raw %}
Jill has {{ daughter.children.length + son.children.length }} grandchildren
{% endraw %}
may result in:
Jill has 100 grandchildrenFilters
{{site.project_title}} supports filters for for use
with expressions.
Single-use bindings
Experimental
Anywhere you use {% raw %}{{}}{% endraw %} in expressions, you can now use double brackets ([[]])
to setup a one-time binding. The binding becomes inactive after {{site.project_title}}
sets its value for the first time.
Example:
One time bindings can potentially be a performance win if you don't need the overhead of setting up property observation.
Named scopes
Named scopes are useful for referencing a model value from an "outer" model "scope".
For example:
{% raw %}
{{ user.name }}
{{ user.name }} owners {{ file.name }}
{% endraw %}
You can also use the key/value notation to get at the index of the current iteration:
{% raw %}
{{ user.name }}
{{ i }}:{{ j }} {{ user.name }} owners {{ file.name }}
{% endraw %}
The scope naming is available (but optional) inside the template, bind, and repeat directives.
{% raw %}
bindsyntax:<template bind="{{ expression as identifier }}">repeatsyntax:<template repeat="{{ identifier in expression }}">,<template repeat="{{ val, i in expression }}">
{% endraw %}
Note: that expression can be a simple identifier, a path or a full
expression (including Object and Array literals).
{: .alert .alert-info }
Nested scoping rules
If a <template> using a named scoped contains children <template>s,
all ancestor scopes are visible, up-to and including the first ancestor not using a named scope.
{% raw %}
<template bind="{{ foo.bar as bar }}">
<template bind="{{ bar.bat }}">
<template bind="{{ boo as bot }}">
</template>
</template>
</template>
</template>{% endraw %}
Binding to attributes
Binding expressions to certain attributes can produce side effects in browsers that don't implement <template> natively. For example, running {% raw %}``{% endraw %} under the polyfill produces a network request that 404s.
In addition, browsers such as IE sanitize certain attributes, disallowing {% raw %}{{}}{% endraw %} replacements in their text.
To counterattack these side effects, bindings in certain attributes can be prefixed with "_":
{% raw %}
<a _href="{{ url }}">Link</a>{% endraw %}
File: docs/polymer/filters.md
layout: default title: Filters
{{site.project_title}} supports several default filters for working with data. They're used in {% raw %}{{}}{% endraw %} expressions by piping an object (containing key/value pairs) to the filter:
{% raw %}
{{ {...} | filterName }}
{% endraw %}
The value of the mustache will include the space-separated keys from the object whose corresponding expressions are truthy.
tokenList
The tokenList filter is useful for binding to the class attribute. It allows you
to dynamically set/remove class names based on the object passed to it. If the object
key is truthy, the name will be applied as a class.
For example:
{% raw %}
{% endraw %}
results in the follow if user.selected == true and user.type == 'super':
styleObject
For simple property values, {{site.project_title}} allows you to bind to the style
attribute directory:
{% raw %}
{{color}}
{% endraw %}
If the element's color property is "red", this results in the following:
redHowever, for the non-literal case (the binded value is an object) use the styleObject filter:
{% raw %}
...
{{color}}{% endraw %}
In this examples styles is an object of the form {color: 'red', background: 'blue'}.
File: docs/polymer/styling.md
layout: default title: Styling reference
{% include toc.html %}
Note: styling {{site.project_title}} elements is no different than styling custom elements.
For a complete guide on the basics, see "A Guide to Styling Elements".
{: .alert }
In addition to the standard features for styling Custom Elements, {{site.project_title}} contains extra goodies for fully controlling element styling. This document outlines those features, including FOUC prevention, the specifics on how the the Shadow DOM polyfill applies styles, and workarounds for current limitations.
Inheriting / resetting outside styles
{{site.project_title}} has first-class support for Shadow DOM's applyAuthorStyles and resetStyleInheritance. To use with your element, define either in your prototype:
Polymer('x-foo', {
applyAuthorStyles: true,
resetStyleInheritance: true
...
});For more information, see "A Guide to Styling Elements".
FOUC prevention
Before custom elements upgrade they may display incorrectly. To help mitigate FOUC issues, {{site.project_title}} provides
a polyfill solution for the :unresolved pseudo class. The [unresolved] attribute initially hides the element it's applied to:
<x-foo unresolved>If you see me, elements are upgraded!</x-foo>Class name | Behavior when applied to an element
|-[unresolved] | Makes the element opacity: 0; display: block; overflow: hidden.[resolved] | Fades-in the element over 200ms.
{: .table }
Upon WebComponentsReady firing, {{site.project_title}} runs the following steps:
- removes the
[unresolved]attribute from elements that have it - adds the
[resolved]attribute - removes
[resolved]on the firsttransitionendevent the element receives
Unveiling elements after boot time {#unveilafterboot}
The veiling process can be used to prevent FOUC at times other than page load. To do so, apply the [unresolved] attribute to the desired elements and swap it out for the [resolved] attribute when the elements should be displayed. For example,
element.setAttribute('unresolved', '');
// ... some time late ...
element.setAttribute('resolved', '');
element.removeAttribute('unresolved');Polyfill styling directives
When running under the polyfill, {{site.project_title}} has @polyfill-*
directives to give you more control for how Shadow DOM styling is shimmed.
@polyfill {#at-polyfill}
The @polyfill directive is used to replace a native CSS selector with one that
will work under the polyfill. For example, targeting distributed nodes using ::content only works under native Shadow DOM. Instead, you can tell {{site.project_title}} to replace said
rules with ones compatible with the polyfill.
To replace native rules, place @polyfill inside a CSS comment above the
style rule you want to replace. The string next to "@polyfill" indicates a
CSS selector to replace the next style rule with. For example:
/* @polyfill .bar */
::content .bar {
color: red;
}
/* @polyfill :host > .bar */
::content .bar {
color: blue;
}
/* @polyfill .container > * */
::content > * {
border: 1px solid black;
}Under native Shadow DOM nothing changes. Under the polyfill the native selector
s replaced with the one in the @polyfill comment above it:
x-foo .bar {
color: red;
}
x-foo > .bar {
color: blue;
}
x-foo .container > * {
border: 1px solid black;
}Tip: If you use a CSS preprocessor, be careful that it doesn't strip out the @polyfill comments.
{: .alert .alert-error }
@polyfill-rule {#at-polyfill-rule}
The @polyfill-rule directive is used to create a style rule that should apply only when the Shadow DOM polyfill is in use. It's a useful catch-all when it's not possible to write a rule that can automatically morph between native Shadow DOM and polyfill Shadow DOM. Because of the simulated styling {{site.project_title}} provides, you should rarely need to use this directive.
To create a rule that only applies under the polyfill, place the @polyfill-rule directive entirely inside a CSS comment:
/* @polyfill-rule .foo {
background: red;
} */
/* @polyfill-rule :host.foo .bar {
background: blue;
} */This has no effect under native Shadow DOM but under the polyfill, the comment is removed and the selector prefixed with the element name:
x-foo .foo {
background: red;
}
x-foo.foo .bar {
background: blue;
}@polyfill-unscoped-rule {#at-polyfill-unscoped-rule}
@polyfill-unscoped-rule is exactly the same as @polyfill-rule except that the rules inside it are not scoped by the polyfill. The rule you write is exactly what will be applied.
@polyfill-unscoped-rule should only be needed in rare cases. {{site.project_title}} uses CSSOM to modify styles and there are a several known rules that don't round-trip correctly via CSSOM (on some browsers). One example using CSS calc() in Safari. It's only in these rare cases that @polyfill-unscoped-rule should be used.
{%comment%}
Making styles global
According to CSS spec, certain @-rules like @keyframe and @font-face
cannot be defined in a <style scoped>. Therefore, they will not work in Shadow DOM.
Instead, you'll need to declare their definitions outside the element.
Stylesheets and <style> elements in an HTML import are included in the main document automatically:
<link rel="stylesheet" href="animations.css">
<template>...</template>Example of defining a global <style>:
<style>
@-webkit-keyframes blink {
to { opacity: 0; }
}
</style>
<template>
<style>
:host {
-webkit-animation: blink 1s cubic-bezier(1.0,0,0,1.0) infinite 1s;
}
</style>
...
</template>{{site.project_title}} also supports making a <style> or inline stylesheet global using thepolymer-scope="global" attribute.
Example: making a stylesheet global
<template>
<link rel="stylesheet" href="fonts.css" polymer-scope="global">
...
</template>Stylsheets that use polymer-scope="global" are moved to the <head> of the main page. This happens once.
Example: Define and use CSS animations in an element
<template>
<style polymer-scope="global">
@-webkit-keyframes blink {
to { opacity: 0; }
}
</style>
<style>
:host {
-webkit-animation: blink 1s cubic-bezier(1.0,0,0,1.0) infinite 1s;
}
</style>
...
</template>Note: polymer-scope="global" should only be used for stylesheets or <style>
that contain rules which need to be in the global scope (e.g. @keyframe and @font-face).
{: .alert .alert-error}
{%endcomment%}
Polyfill details
Handling scoped styles
Native Shadow DOM gives us style encapsulation for free via scoped styles. For browsers
that lack native support, {{site.project_title}}'s polyfills attempt to shim some
of the scoping behavior.
Because polyfilling the styling behaviors of Shadow DOM is difficult, {{site.project_title}}
has opted to favor practicality and performance over correctness. For example,
the polyfill's do not protect Shadow DOM elements against document level CSS.
When {{site.project_title}} processes element definitions, it looks for <style> elements
and stylesheets. It removes these from the custom element's Shadow DOM <template>, rejiggers them according to the rules below, and appends a <style> element to the main document with the reformulated rules.
Reformatting rules
Replace
:host, including:host(<compound selector>)by prefixing with the element's tag nameFor example, these rules inside an
x-foo:text<template> <style> :host { ... } :host:hover { ... } :host(.foo) > .bar { ... } </style> ...becomes:
text<template> <style> x-foo { ... } x-foo:hover { ... } x-foo.foo > .bar, .foo x-foo > bar {...} </style> ...Prepend selectors with the element name, creating a descendant selector.
This ensures styling does not leak outside the element's shadowRoot (e.g. upper bound encapsulation).For example, this rule inside an
x-foo:text<template> <style> div { ... } </style> ...becomes:
text<template> <style> x-foo div { ... } </style> ...Note, this technique does not enforce lower bound encapsulation. For that,
you need to forcing strict styling.Replace
^and^^with a<space>character.
Forcing strict styling {#strictstyling}
By default, {{site.project_title}} does not enforce lower bound styling encapsulation.
The lower bound is the boundary between insertion points and the shadow host's children.
You can turn lower bound encapsulation by setting Platform.ShadowCSS.strictStyling:
Platform.ShadowCSS.strictStyling = trueThis isn't the yet the default because it requires that you add the custom element's name as an attribute on all DOM nodes in the shadowRoot (e.g. ``).
File: docs/elements/index.md
layout: default title: Polymer element reference
Note: These elements and their documentation are very early.
The "source" links below point to commented code where you can learn more about
how to use the element.
{: .alert .alert-info}
{% include toc.html %}
Elements
Utility elements that do not render UI
Install everything:
bower install Polymer/polymer-elements{% directory dir:components tag:div glob:polymer-* blacklistglob:polymer-ui-* blacklist:"polymer-dev polymer-expressions polymer-elements polymer-ui-elements" %}
UI Elements
Basic UI components
Install everything:
bower install Polymer/polymer-ui-elementsFile: docs/elements/other.md
layout: default title: Toolkit-UI Elements
Old and possibly outdated UI experiments.
Repository: github.com/Polymer/toolkit-ui
More Elements
Extra components and wrappers for third-party code.
Repository: github.com/Polymer/more-elements