29 | A robust, production-ready library to observe CSS property changes.
30 | Detects browser bugs and works around them, so you don't have to.
31 |
32 |
33 | [](https://www.npmjs.com/package/style-observer)
34 | [](https://pkg-size.dev/style-observer)
35 |
36 | - ✅ Observe changes to custom properties
37 | - ✅ Observe changes to standard properties (except `transition` and `animation`)
38 | - ✅ Observe changes on any element (including those in Shadow DOM)
39 | - ✅ [Lightweight](https://pkg-size.dev/style-observer), ESM-only code, with no dependencies
40 | - ✅ [200+ unit tests](tests) you can run in your browser of choice
41 | - ✅ Throttling per element
42 | - ✅ Does not overwrite existing transitions
43 |
44 | ## Compatibility
45 |
46 |
91 |
92 | ## Install
93 |
94 | The quickest way is to just include straight from the [Netlify](https://www.netlify.com/) CDN:
95 |
96 | ```js
97 | import StyleObserver from "https://observe.style/index.js";
98 | ```
99 |
100 | This will always point to the latest version, so it may be a good idea to eventually switch to a local version that you can control.
101 | E.g. you can use npm:
102 |
103 | ```sh
104 | npm install style-observer
105 | ```
106 |
107 | and then, if you use a bundler like Rollup or Webpack:
108 |
109 | ```js
110 | import StyleObserver from "style-observer";
111 | ```
112 |
113 | and if you don’t:
114 |
115 | ```js
116 | import StyleObserver from "node_modules/style-observer/dist/index.js";
117 | ```
118 |
119 | ## Usage
120 |
121 | You can first create the observer instance and then observe, like a `MutationObserver`.
122 | The simplest use is observing a single property on a single element:
123 |
124 | ```js
125 | const observer = new StyleObserver(records => console.log(records));
126 | observer.observe(document.querySelector("#my-element"), "--my-custom-property");
127 | ```
128 |
129 | You can also observe multiple properties on multiple elements:
130 |
131 | ```js
132 | const observer = new StyleObserver(records => console.log(records));
133 | const properties = ["color", "--my-custom-property"];
134 | const targets = document.querySelectorAll(".my-element");
135 | observer.observe(targets, properties);
136 | ```
137 |
138 | You can also provide both targets and properties when creating the observer,
139 | which will also call `observe()` for you:
140 |
141 | ```js
142 | import StyleObserver from "style-observer";
143 |
144 | const observer = new StyleObserver(callback, {
145 | targets: document.querySelectorAll(".my-element"),
146 | properties: ["color", "--my-custom-property"],
147 | });
148 | ```
149 |
150 | Both targets and properties can be either a single value or an iterable.
151 |
152 | Note that the observer will not fire immediately for the initial state of the elements (i.e. it behaves like `MutationObserver`, not like `ResizeObserver`).
153 |
154 | ### Records
155 |
156 | Just like other observers, changes that happen too close together (set the `throttle` option to configure) will only invoke the callback once,
157 | with an array of records, one for each change.
158 |
159 | Each record is an object with the following properties:
160 |
161 | - `target`: The element that changed
162 | - `property`: The property that changed
163 | - `value`: The new value of the property
164 | - `oldValue`: The previous value of the property
165 |
166 | ## Future Work
167 |
168 | - Observe pseudo-elements
169 | - `immediate` convenience option that fires the callback immediately for every observed element
170 |
171 | ## Limitations & Caveats
172 |
173 | - You cannot observe changes on elements **not connected to a document**. However, once the elements become connected again, the observer will pick up any changes that happened while they were disconnected.
174 | - You cannot observe changes to `transition` and `animation` properties (and their constituent properties).
175 | - You cannot observe changes **caused by CSS animations** (follow [#87](https://github.com/LeaVerou/style-observer/issues/87) for updates).
176 | - Changes caused due to a slotted element being moved to a different slot will not be picked up.
177 |
178 | ### Changing `transition` properties after observing
179 |
180 | If you change the `transition`/`transition-*` properties dynamically on elements you are observing after you start observing them,
181 | the easiest way to ensure the observer continues working as expected is to call `observer.updateTransition(targets)` to regenerate the `transition` property the observer uses to detect changes.
182 |
183 | If running JS is not an option, you can also do it manually:
184 |
185 | 1. Add `, var(--style-observer-transition, --style-observer-noop)` at the end of your `transition` property.
186 | E.g. if instead of `transition: 1s background` you'd set `transition: 1s background, var(--style-observer-transition, --style-observer-noop)`.
187 | 2. Make sure to also set `transition-behavior: allow-discrete;`.
188 |
189 | ## Prior Art
190 |
191 | The quest for a JS style observer has been long and torturous.
192 |
193 | - Early attempts used polling. Notable examples were [`ComputedStyleObserver` by Keith Clark](https://github.com/keithclark/ComputedStyleObserver)
194 | and [`StyleObserver` by PixelsCommander](https://github.com/PixelsCommander/StyleObserver)
195 | - [Jane Ori](https://propjockey.io) was the first to do better than polling, her [css-var-listener](https://github.com/propjockey/css-var-listener) using a combination of observers and events.
196 | - [css-variable-observer](https://github.com/fluorumlabs/css-variable-observer) by [Artem Godin](https://github.com/fluorumlabs) pioneered using transition events to observe property changes, and used an ingenious hack based on `font-variation-settings` to observe CSS property changes.
197 | - Four years later, [Bramus Van Damme](https://github.com/bramus) pioneered a way to do it "properly" in [style-observer](https://github.com/bramus/style-observer),
198 | thanks to [`transition-behavior: allow-discrete`](https://caniuse.com/mdn-css_properties_transition-behavior) becoming Baseline and even [blogged about all the bugs he encountered along the way](https://www.bram.us/2024/08/31/introducing-bramus-style-observer-a-mutationobserver-for-css/).
199 |
200 | While `StyleObserver` builds on this body of work, it is not a fork of any of them.
201 | It was written from scratch with the explicit goal of extending browser support and robustness.
202 | [Read the blog post](https://lea.verou.me/2025/style-observer/) for more details.
203 |
204 |
205 |