{"id":25590606,"url":"https://github.com/LeaVerou/style-observer","last_synced_at":"2026-04-11T16:30:20.962Z","repository":{"id":277130884,"uuid":"916468643","full_name":"LeaVerou/style-observer","owner":"LeaVerou","description":"Run JS when a CSS property changes. Any CSS property.","archived":false,"fork":false,"pushed_at":"2025-02-19T14:59:05.000Z","size":374,"stargazers_count":342,"open_issues_count":5,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-19T15:47:24.441Z","etag":null,"topics":["css","observer","style-observer"],"latest_commit_sha":null,"homepage":"https://observe.style","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LeaVerou.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-14T06:43:59.000Z","updated_at":"2025-02-19T15:09:32.000Z","dependencies_parsed_at":"2025-02-12T10:25:37.973Z","dependency_job_id":"f11af173-386d-4c52-a953-fcd1029b40c5","html_url":"https://github.com/LeaVerou/style-observer","commit_stats":null,"previous_names":["leaverou/style-observer"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeaVerou%2Fstyle-observer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeaVerou%2Fstyle-observer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeaVerou%2Fstyle-observer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeaVerou%2Fstyle-observer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LeaVerou","download_url":"https://codeload.github.com/LeaVerou/style-observer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239985718,"owners_count":19729514,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","observer","style-observer"],"created_at":"2025-02-21T09:02:02.482Z","updated_at":"2026-04-11T16:30:18.903Z","avatar_url":"https://github.com/LeaVerou.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cheader class=\"wa-split\"\u003e\n\n# \u003cimg src=\"assets/logo.svg\" class=\"logo\" width=\"60\"\u003e Style \u003cspan\u003eObserver\u003c/span\u003e\n\n\u003cnav\u003e\n\t\u003ca href=\"/api\"\u003eAPI\u003c/a\u003e\n\t\u003cspan class=\"readme-only\"\u003e·\u003c/span\u003e\n\t\u003ca href=\"/tests\"\u003eTests\u003c/a\u003e\n\t\u003ca href=\"https://github.com/leaverou/style-observer\" target=\"_blank\"\u003e\n\t\t\u003ci class=\"fab fa-github\"\u003e\u003c/i\u003e\n\t\u003c/a\u003e\n\t\u003chr class=\"readme-only\" /\u003e\n\u003c/nav\u003e\n\n\u003c/header\u003e\n\u003cdiv class=\"page\"\u003e\n\u003caside\u003e\n\n- [Install](#install)\n- [Usage](#usage)\n- [Future Work](#future-work)\n- [Limitations \u0026 Caveats](#limitations-%26-caveats)\n- [Prior Art](#prior-art)\n\n\u003c/aside\u003e\n\u003cmain\u003e\n\n\u003cp class=\"blurb\"\u003e\nA robust, production-ready library to observe CSS property changes.\nDetects browser bugs and works around them, so you don't have to.\n\u003c/p\u003e\n\n[![npm](https://img.shields.io/npm/v/style-observer)](https://www.npmjs.com/package/style-observer)\n[![gzip size](https://img.shields.io/badge/gzip-2.73kB-blue)](https://pkg-size.dev/style-observer)\n\n- \u003cspan\u003e✅\u003c/span\u003e Observe changes to custom properties\n- \u003cspan\u003e✅\u003c/span\u003e Observe changes to standard properties (except `display`, `transition`, `animation`)\n- \u003cspan\u003e✅\u003c/span\u003e Observe changes on any element (including those in Shadow DOM)\n- \u003cspan\u003e✅\u003c/span\u003e [Lightweight](https://pkg-size.dev/style-observer), ESM-only code, with no dependencies\n- \u003cspan\u003e✅\u003c/span\u003e [200+ unit tests](tests) you can run in your browser of choice\n- \u003cspan\u003e✅\u003c/span\u003e Throttling per element\n- \u003cspan\u003e✅\u003c/span\u003e Does not overwrite existing transitions\n\n## Compatibility\n\n\u003cdiv class=\"scrollable\"\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\t\u003cth\u003eFeature\u003c/th\u003e\n\t\u003cth\u003e\u003ci class=\"fab fa-chrome\"\u003e\u003c/i\u003e Chrome\u003c/th\u003e\n\t\u003cth\u003e\u003ci class=\"fab fa-safari\"\u003e\u003c/i\u003e Safari\u003c/th\u003e\n\t\u003cth\u003e\u003ci class=\"fab fa-firefox\"\u003e\u003c/i\u003e Firefox\u003c/th\u003e\n\t\u003cth\u003e% of global users\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\t\u003ctd\u003eCustom properties\u003c/td\u003e\n\t\u003ctd\u003e117\u003c/td\u003e\n\t\u003ctd\u003e17.4\u003c/td\u003e\n\t\u003ctd\u003e129\u003c/td\u003e\n\t\u003ctd\u003e\u003ca href=\"https://caniuse.com/mdn-css_properties_transition-behavior\"\u003e89%\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\t\u003ctd\u003eCustom properties (registered with an animatable type)\u003c/td\u003e\n\t\u003ctd\u003e97\u003c/td\u003e\n\t\u003ctd\u003e16.4\u003c/td\u003e\n\t\u003ctd\u003e128\u003c/td\u003e\n\t\u003ctd\u003e\u003ca href=\"https://caniuse.com/mdn-api_css_registerproperty_static\"\u003e93%\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\t\u003ctd\u003eStandard properties (discrete)\n\t\u003cbr\u003e\u003csmall class=\"compat wa-caption-s\"\u003eExcept \u003ccode\u003edisplay\u003c/code\u003e, \u003ccode\u003etransition\u003c/code\u003e, \u003ccode\u003eanimation\u003c/code\u003e\u003c/small\u003e\n\t\u003c/td\u003e\n\t\u003ctd\u003e117\u003c/td\u003e\n\t\u003ctd\u003e17.4\u003c/td\u003e\n\t\u003ctd\u003e129\u003c/td\u003e\n\t\u003ctd\u003e\u003ca href=\"https://caniuse.com/mdn-css_properties_transition-behavior\"\u003e89%\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\t\u003ctd\u003eStandard properties (animatable)\u003c/td\u003e\n\t\u003ctd\u003e97\u003c/td\u003e\n\t\u003ctd\u003e15.4\u003c/td\u003e\n\t\u003ctd\u003e104\u003c/td\u003e\n\t\u003ctd\u003e95%\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\n## Install\n\nThe quickest way is to just include straight from the [Netlify](https://www.netlify.com/) CDN:\n\n```js\nimport StyleObserver from \"https://observe.style/index.js\";\n```\n\nThis 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.\nE.g. you can use npm:\n\n```sh\nnpm install style-observer\n```\n\nand then, if you use a bundler like Rollup or Webpack:\n\n```js\nimport StyleObserver from \"style-observer\";\n```\n\nand if you don’t:\n\n```js\nimport StyleObserver from \"node_modules/style-observer/dist/index.js\";\n```\n\n## Usage\n\nYou can first create the observer instance and then observe, like a `MutationObserver`.\nThe simplest use is observing a single property on a single element:\n\n```js\nconst observer = new StyleObserver(records =\u003e console.log(records));\nobserver.observe(document.querySelector(\"#my-element\"), \"--my-custom-property\");\n```\n\nYou can also observe multiple properties on multiple elements:\n\n```js\nconst observer = new StyleObserver(records =\u003e console.log(records));\nconst properties = [\"color\", \"--my-custom-property\"];\nconst targets = document.querySelectorAll(\".my-element\");\nobserver.observe(targets, properties);\n```\n\nYou can also provide both targets and properties when creating the observer,\nwhich will also call `observe()` for you:\n\n```js\nimport StyleObserver from \"style-observer\";\n\nconst observer = new StyleObserver(callback, {\n\ttargets: document.querySelectorAll(\".my-element\"),\n\tproperties: [\"color\", \"--my-custom-property\"],\n});\n```\n\nBoth targets and properties can be either a single value or an iterable.\n\nNote that the observer will not fire immediately for the initial state of the elements (i.e. it behaves like `MutationObserver`, not like `ResizeObserver`).\n\n### Records\n\nJust like other observers, changes that happen too close together (set the `throttle` option to configure) will only invoke the callback once,\nwith an array of records, one for each change.\n\nEach record is an object with the following properties:\n- `target`: The element that changed\n- `property`: The property that changed\n- `value`: The new value of the property\n- `oldValue`: The previous value of the property\n\n## Future Work\n\n- Observe pseudo-elements\n- `immediate` convenience option that fires the callback immediately for every observed element\n\n## Limitations \u0026 Caveats\n\n### Disconnected elements\n\nYou cannot observe changes on elements not connected to a document.\n\n### Transitions \u0026 Animations\n\n- You cannot observe `transition` and `animation` properties.\n- You cannot observe changes caused by CSS animations.\n\n### Observing `display`\n\nObserving `display` is inconsistent across browsers (see [relevant tests](tests/?test=display)):\n\n\u003cdiv class=\"scrollable\"\u003e\n\n| Rule | Chrome | Firefox | Safari | Safari (iOS) | Samsung Internet |\n| --- | --- | --- | --- | --- | --- |\n| From `display: none` | ❌ | ❌ | ❌ | ❌ | ❌ |\n| To `display: none` | ❌ | ❌ | ✅ | ✅ | ❌ |\n| From not `none` to not `none` |  ✅ | ❌ | ✅ | ✅ | ✅ |\n\n\u003c/div\u003e\n\nTo observe elements becoming visible or not visible, you may want to take a look at [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).\n\n### Changing `transition` properties after observing\n\nIf you change the `transition`/`transition-*` properties dynamically on elements you are observing after you start observing them,\nthe 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.\n\nIf running JS is not an option, you can also do it manually:\n1. Add `, var(--style-observer-transition, --style-observer-noop)` at the end of your `transition` property.\nE.g. if instead of `transition: 1s background` you'd set `transition: 1s background, var(--style-observer-transition, --style-observer-noop)`.\n2. Make sure to also set `transition-behavior: allow-discrete;`.\n\n## Prior Art\n\nThe quest for a JS style observer has been long and torturous.\n\n- Early attempts used polling. Notable examples were [`ComputedStyleObserver` by Keith Clark](https://github.com/keithclark/ComputedStyleObserver)\nand [`StyleObserver` by PixelsCommander](https://github.com/PixelsCommander/StyleObserver)\n- [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.\n- [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.\n- 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),\nthanks 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/).\n\nWhile `StyleObserver` builds on this body of work, it is not a fork of any of them.\nIt was written from scratch with the explicit goal of extending browser support and robustness.\n[Read the blog post](https://lea.verou.me/2025/style-observer/) for more details.\n\n\u003c/main\u003e\n\u003c/div\u003e\n\u003cfooter\u003e\n\u003chr class=\"readme-only\" /\u003e\n\nBy [Lea Verou](https://lea.verou.me/) and [Dmitry Sharabin](https://d12n.me/).\n\u003c/footer\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLeaVerou%2Fstyle-observer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FLeaVerou%2Fstyle-observer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLeaVerou%2Fstyle-observer/lists"}