CSS Houdini: What It Is and How to Use It
CSS Houdini is a family of APIs for extending CSS-related behavior. Start with typed custom properties, then explore Paint Worklets with a fallback.
CSS Houdini is an umbrella name for browser APIs that expose parts of the CSS engine so developers can work with CSS values and rendering behavior more directly. It is a family of APIs, not one feature. A practical first step is @property, which gives a custom property a declared type, inheritance behavior, and initial value. Paint Worklets go further by generating CSS image values from JavaScript, but their browser support is more limited.
Use each API only after checking support in the browsers and devices you need to serve. Keep ordinary CSS fallbacks for optional effects, and do not make essential content depend on a worklet.
1. What CSS Houdini includes
Houdini APIs expose different parts of CSS. Some are useful in current projects; others remain limited or experimental. The name does not mean that every browser lets you invent arbitrary CSS syntax.
| API area | What it does | Practical status |
|---|---|---|
| Properties and Values | Registers custom properties with syntax, inheritance, and an initial value through @property or CSS.registerProperty(). |
MDN marks it Baseline 2024. Check older browsers and devices you support. |
| CSS Painting | Lets a Paint Worklet generate an image value referenced with paint(). |
Limited availability; provide a fallback. |
| CSS Typed OM | Represents CSS values as typed JavaScript objects instead of strings. | Limited availability; check target browsers. |
| CSS Layout API | Explores author-defined layout algorithms. | Work in progress, not a general production feature. |
| CSS Parser API and Font Metrics API | Proposed ways to expose parsing and font-metric behavior. | MDN’s overview describes these as proposals without browser implementations. |
See the MDN Houdini APIs overview for the API map. A published specification or draft does not by itself mean that a feature is implemented interoperably.
2. Start with a typed custom property
Ordinary custom properties hold token sequences. Registering one tells the browser what kind of value it is meant to contain, whether it inherits, and what value to use initially. This does not create a new built-in CSS property; it defines behavior for an author-named custom property.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Typed CSS custom property</title>
<style>
@property --progress {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
.meter {
--progress: 0.65;
width: 12rem;
height: 1rem;
border-radius: 999px;
background: linear-gradient(
to right,
#1769aa calc(var(--progress) * 100%),
#dce3ea 0
);
}
</style>
<div class="meter" role="img" aria-label="Progress: 65 percent"></div>
Save this as an HTML file and open it in a browser that supports the Properties and Values API. The syntax descriptor is a CSS type grammar, inherits controls whether the value flows from an ancestor, and initial-value supplies the registered property’s default. A registered property requires an initial value valid for its syntax.
The syntax can describe types such as <number>, <length>, <color>, and other supported CSS value forms. Choose the narrowest useful grammar for the property. For example, a property intended to hold a length should not accept an arbitrary token sequence.
Register from JavaScript
Use CSS.registerProperty() when registration belongs naturally to script setup. Do not register the same property name more than once in a document; duplicate registration can throw an exception.
if ("registerProperty" in CSS) {
try {
CSS.registerProperty({
name: "--card-angle",
syntax: "<angle>",
inherits: false,
initialValue: "0deg"
});
} catch (error) {
// A duplicate registration or invalid descriptor can throw.
console.error("Could not register --card-angle", error);
}
}
Prefer the stylesheet form when the registration is part of the component’s CSS. The JavaScript form is useful when script owns initialization. The Properties and Values API reference and guide document both approaches.
3. Generate an image with a Paint Worklet
A Paint Worklet registers a painter under a name. CSS can then use that name in paint(name) where an image value is accepted, often as a background. The painter receives the output dimensions and can read declared CSS properties.
The example below paints diagonal stripes controlled by two custom properties. It also defines a gradient fallback. Save the files in the same directory and serve them from a local HTTP server; worklet module loading requires a supported, secure context. For example, many developers use a framework’s local dev server or another static server.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Paint Worklet stripes</title>
<style>
@property --stripe-size {
syntax: "<length>";
inherits: false;
initial-value: 12px;
}
@property --stripe-color {
syntax: "<color>";
inherits: false;
initial-value: #1769aa;
}
.panel {
--stripe-size: 14px;
--stripe-color: #1769aa;
min-height: 12rem;
padding: 2rem;
color: white;
background: linear-gradient(135deg, #1769aa, #65a9d8);
background-image: paint(stripes);
}
</style>
<section class="panel">Decorative pattern</section>
<script>
if ("paintWorklet" in CSS) {
CSS.paintWorklet.addModule("stripes.js").catch((error) => {
console.error("Paint Worklet failed to load", error);
});
}
</script>
</html>
// stripes.js — loaded as a Paint Worklet module
class StripesPainter {
static get inputProperties() {
return ["--stripe-size", "--stripe-color"];
}
paint(context, size, properties) {
const rawSize = properties.get("--stripe-size");
const stripeSize = rawSize ? rawSize.value : 12;
const rawColor = properties.get("--stripe-color");
const color = rawColor ? rawColor.toString() : "#1769aa";
context.fillStyle = color;
const step = Math.max(2, stripeSize * 2);
for (let x = -size.height; x < size.width; x += step) {
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x + size.height, size.height);
context.lineTo(x + size.height + stripeSize, size.height);
context.lineTo(x + stripeSize, 0);
context.closePath();
context.fill();
}
}
}
registerPaint("stripes", StripesPainter);
The fallback gradient is declared first. If the browser does not support paint() or the worklet does not load, the panel still has a background. Keep the worklet’s result decorative; provide text, contrast, and meaning through ordinary HTML and CSS.
For the API’s exact behavior and support status, consult MDN’s CSS Painting API and paint() reference, plus the W3C CSS Painting API specification.
4. Use Typed OM when structured CSS values help
The CSS Typed Object Model (Typed OM) represents values with objects that expose their type and, for numeric values, units and numeric components. This can make calculations and value manipulation clearer than building and parsing strings. For example, a supported browser may expose a computed width as a CSSUnitValue:
const element = document.querySelector(".panel");
if (element && "computedStyleMap" in element) {
const width = element.computedStyleMap().get("width");
if (width instanceof CSSUnitValue) {
console.log(width.value, width.unit);
}
} else {
// Compatibility fallback: computed styles are strings.
const width = getComputedStyle(element).width;
console.log(width);
}
Typed OM has limited availability, so feature-detect it and retain a string-based alternative where needed. Read MDN’s Typed OM reference and guide.
5. Choose an API by the problem
- Need a custom value that animates or has a defined type? Try a registered custom property.
- Need to inspect or change CSS values in JavaScript? Use ordinary style APIs unless Typed OM materially simplifies the work and your support matrix permits it.
- Need a procedural decorative image tied to CSS values or element size? Consider a Paint Worklet, with an ordinary CSS or image fallback.
- Need a custom layout algorithm or parser behavior? First check whether standard layout features solve the problem. Treat Layout and Parser proposals as exploratory, not general-purpose production dependencies.
Compare choices by the job they solve, browser support in your target set, fallback cost, implementation complexity, and whether a standard CSS feature already meets the need. The APIs do not establish a universal speed advantage; measure your own implementation if performance is a concern.
6. Check browser support and plan fallbacks
There is no single answer to “Is Houdini supported?” Support differs by API. MDN marks the Properties and Values API Baseline 2024, while Painting and Typed OM are not Baseline and remain unavailable in some widely used browsers. Baseline is a compatibility summary, not a guarantee for every older device, embedded webview, or user environment.
- Check the compatibility table for the exact API you plan to use.
- Test the browsers, devices, and embedded webviews in your support policy.
- Use feature detection, such as checking
"paintWorklet" in CSSor"registerProperty" in CSS, and still test real rendering. - Keep critical content and functionality independent of Paint Worklets.
- Define ordinary CSS fallback values before experimental enhancement.
- Recheck compatibility information before release because support status can change.
References: MDN on Properties and Values, Painting, Typed OM, and the Baseline compatibility glossary. The W3C Layout API document explicitly cautions that it is work in progress.
7. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| The registered property behaves like an untyped custom property. | The browser does not support the Properties and Values API, or the registration did not load. | Check support in that browser, inspect the console, and make sure the stylesheet containing @property is loaded. Keep a valid ordinary custom-property fallback. |
| The property uses its initial value instead of the value you set. | The assigned value does not match the declared syntax, or the declaration is overridden. | Check the value against syntax, inspect the cascade, and confirm the initial value is valid for the declared type. |
CSS.registerProperty() throws. |
The name may already be registered, or a descriptor may be invalid. | Register once, use a unique custom property name, and verify name, syntax, inherits, and initialValue. |
| The Paint Worklet background is missing. | The browser may not support Painting, the module may have failed to load, the worklet name may not match, or the CSS property may not accept the value in that environment. | Check "paintWorklet" in CSS, the network request for the module, console errors, and the matching name in registerPaint() and paint(). Confirm the ordinary fallback appears. |
| The worklet cannot be loaded from a local file. | Worklet modules are loaded as modules and local-file restrictions can interfere. | Serve the page over a supported local HTTP development server or a secure origin, and check the browser console for loading or security errors. |
| The worklet ignores a custom property. | The property was not listed in inputProperties, the property name differs, or the value was not handled as expected. |
Declare every property the painter reads, match the spelling exactly, and handle absent or unexpected values safely. |
| Typed OM methods are undefined. | The browser does not implement the relevant Typed OM interface. | Feature-detect the specific method and fall back to getComputedStyle() or standard style properties. |
8. Performance, reliability, and cost
Houdini is a capability set, not a performance promise. A Paint Worklet can generate a reusable CSS image based on size and style inputs, but the sources do not establish that it is faster than an equivalent CSS effect or static asset. Keep paint logic bounded, avoid unnecessary complexity, and profile the actual page on target devices.
For reliability, keep progressive enhancement simple: render a usable baseline first, then apply the worklet effect where supported. Make sure a failed module load leaves the element legible and visually acceptable. Typed properties can clarify value handling, but do not rely on unsupported registration for required behavior.
There is no Houdini API fee described by these platform references. The practical costs are engineering time, compatibility testing, fallback maintenance, and any rendering work performed by the page. Do not assume a worklet reduces runtime or infrastructure costs without measuring your use case.
9. Or skip the browser setup
If your task is to capture how a page renders while you build or document a CSS feature, you can use ScreenshotNeo, a website screenshot API and MCP server for developers. The API can return a screenshot or PDF from one GET request. See the ScreenshotNeo documentation for configuration options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month, with no card required.
10. FAQ
Does Houdini let me add any CSS property I want?
No. Registered custom properties add declared behavior to author-defined names, and Paint Worklets provide generated image values. They do not provide a general mechanism to add arbitrary built-in CSS syntax across browsers.
Should I use a Paint Worklet for an essential interface element?
Keep essential meaning and functionality in HTML and standard CSS. A worklet can enhance a decorative effect, with a fallback for browsers that do not support it.
Is the CSS Layout API ready for general production use?
The cited W3C document describes work in progress. Check current implementation and support status for your exact use case rather than treating a draft as a shipped browser feature.
Where should I begin learning Houdini?
Start with the Properties and Values API, then try a Paint Worklet only if generated imagery solves a real design need. Consult the API-specific MDN references and browser compatibility data as you go.


