prop-for-that: JavaScript's runtime knowledge, handed to CSS as variables
what JS knows, now CSS knows
At a glance
- What is it?
- prop-for-that is a zero-dependency TypeScript library that writes what JavaScript can read, pointer position, viewport size, battery, sensors, element visibility, into live CSS custom properties, batched and diffed down to one setProperty per frame. Bind with an attribute, react with calc and var, and let stylesheets do the reacting instead of render loops.
- Who is it for?
- prop-for-that fits front-end developers who want runtime state, from pointer and viewport to battery, sensors and image colours, consumable directly in CSS, without a framework's reactivity or per-feature event plumbing, and with performance characteristics that are documented precisely enough to trust. It does not fit projects already standardizing reactive bindings for this purpose, or teams unwilling to keep a typed-property discipline over a broad custom-property surface.
- Can I use it commercially?
- Yes. MIT 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 56 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The seam between the two languages, bridged
The premise is a gap every front-end developer knows: sliders, pointer position, element visibility, viewport size, battery, network, sensors, JavaScript can read all of it and CSS cannot see any of it. The usual bridge is an event handler updating a class or a style property, repeated per feature until the component is more plumbing than interface.
prop-for-that bridges it once, generically: runtime state is written into custom properties under live and const prefixes, batched and diffed down to a single setProperty per frame, so CSS can compose and react with plain calc and var. The name of the game is that the stylesheet becomes the reactor and the JavaScript becomes a sensor.
The pedigree is worth noting, because the craft shows: the author is a well-known CSS platform developer, and the repository carries the kind of documentation site, demo gallery, benchmark folder and end-to-end test setup that a platform project gets.
The whole idea in six lines
Install is one package, zero dependencies:
npm i prop-for-thatImport the automatic entry point and annotate an element:
<script type="module">import 'prop-for-that/auto'</script>
<input type="range" data-props-for="range" />Then style from the element's own live value:
input {
background: hsl(calc(var(--live-value-pct) * 120) 80% 50%);
}That is the entire contract: bind any element with the data attribute naming its source keys, read the resulting custom properties in CSS, and the slider paints itself from its own value with no event listeners and no render loop. The example is well chosen, because a range input styling its own track from its own value is the case everyone has hand-rolled badly at least once.
Hoisting, because inheritance runs downhill
The interesting engineering problem in this design is directionality, and the README names it precisely: custom properties inherit downward only, and container style queries match against ancestors, so an element can neither style-query the values written on itself nor share them with siblings. The answer is a second attribute that observes the element but writes its properties on the nearest matching ancestor:
<figure>
<img data-props-for="img" data-props-to="figure" src="art.jpg" />
<figcaption>…</figcaption>
</figure>With the property hoisted, siblings can react through a style query:
@container style(--live-loaded: 0) {
.skeleton { opacity: 1; }
}There is also an imperative form taking a selector or element for the destination, for cases the declarative attribute cannot express. It is a small API surface solving a real platform constraint, and the documentation walks the inheritance rules rather than hiding them, which is the difference between a library that composes and one that surprises.
Fast by design, and honest about the meter
The performance story is specific enough to evaluate. One requestAnimationFrame flush per frame, idle when nothing changes, frozen while the tab is hidden. Write-on-change diffing, so unchanged values cost nothing. A single shared resize observer and intersection observer for the whole page rather than one per element. Continuously-sampling element sources pause while their element is off screen, while event-driven sources like form fields run ungated because they cost nothing idle.
Shipping shape follows the same economy: four core sources built in, viewport, element size, visibility and range values, with everything else an opt-in, tree-shakeable plugin, and under the automatic entry point each plugin loads on demand the moment a data attribute asks for it.
The plugin catalog is broad: pointer position, battery, network and online status, page focus and visibility, navigation type, meta tags, a low-entropy user-agent decomposition, frames per second, clock, scroll velocity, device orientation and motion, geolocation, CPU pressure, soft-keyboard geometry, media playback, form and field state, select and colour-picker values, text-truncation detection, dominant and accent colours extracted from images and video, and seeded per-element random rolls, over twenty sources in all. Typed property opt-in enables interpolation, and a head entry point writes constants before first paint for flash-safe layout decisions like scrollbar width and device pixel ratio.
Entry points, and a manual for machines
Four entry points divide the surface by intent: the automatic module for declarative zero-config binding, the imperative core for explicit control and teardown, a synchronous head build for flash-safe constants, and the plugin catalog. The automatic path sees the light DOM only, with shadow roots bound imperatively, and lazy-loads plugin chunks, which carries one documented deployment caveat: use a content network that serves the package files verbatim rather than a rewriting one.
The detail that marks the project's moment in time is a condensed single-file reference, entry points, the full variable catalog, recipes and gotchas, published for language models and AI tools and shipped inside the package. A library documenting itself for agent consumers, alongside its human docs, is a small sign of the direction tooling is taking, and here it is done as a first-class artefact rather than an afterthought.
Releases are frequent and current, with two patch versions on 2026-07-25, the licence is MIT, and the last push was the same day.
Who reaches for it, and what else is on the shelf
The natural adopter is the developer who wants interface reactions, theming from device state, layout from measurement, progressive enhancement from capability detection, expressed in CSS rather than in a framework's reactivity. It pairs naturally with stylesheet-first architectures and with platforms where a component framework is not already doing the reacting.
The alternatives define the trade. Hand-written event handlers and inline style updates cost nothing in dependencies and everything in repetition, and they scatter the reactive logic across the codebase. A reactive framework with bindings solves the same problem inside its component model, at the cost of the framework; prop-for-that is the framework-free version of that idea, aimed at the stylesheet as the consumer.
The honest limit is architectural discipline: custom properties are stringly typed by default, so the typed-property opt-in and the documented catalog matter for keeping a large surface sane. Used with that discipline, it moves a whole class of glue code out of the script and into the style, where it was always easier to read.
Editorial conclusion
prop-for-that fits front-end developers who want runtime state, from pointer and viewport to battery, sensors and image colours, consumable directly in CSS, without a framework's reactivity or per-feature event plumbing, and with performance characteristics that are documented precisely enough to trust. It does not fit projects already standardizing reactive bindings for this purpose, or teams unwilling to keep a typed-property discipline over a broad custom-property surface. Verify first: the plugin list against the states you actually need, that your content network serves the lazy plugin chunks verbatim, and the head entry point if flash-of-wrong-layout is your enemy. The licence is MIT, and the last push was on 2026-07-25.
Frequently asked questions
What does prop-for-that actually do?
It writes JavaScript-readable runtime state into live CSS custom properties, batched and diffed to a single setProperty per frame, so stylesheets can react with plain calc and var. Elements bind declaratively with an attribute, and over twenty opt-in plugin sources cover everything from pointer position to CPU pressure.
Does prop-for-that have dependencies?
None. It is a zero-dependency TypeScript package shipping ESM and CommonJS builds, SSR-safe, with four core sources built in and every further source as an opt-in, tree-shakeable plugin that the automatic entry point loads on demand.
Why does the library need a props-to attribute?
Custom properties inherit downward only and container style queries match ancestors, so values written on an element cannot serve its siblings or its own style queries. The props-to attribute observes the element but writes its properties on a matching ancestor, making them visible to the surrounding subtree.
Community notes