# shadow-observer

> A MutationObserver that automatically picks up Shadow Roots when/if needed

Latest version **0.1.7** (published 2026-04-26) · MIT license · 0 weekly downloads

## Install

```sh
npm install shadow-observer
pnpm add shadow-observer
yarn add shadow-observer
bun add shadow-observer
```

## Health

**Score 55/100 (C)** — status: active.

Positive: has types; esm support; no vulnerabilities.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.1.7 |
| Published | 2026-04-26 |
| First published | 2026-04-23 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 19.7 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Andrea Giammarchi |
| Maintainers | webreflection |
| Keywords | shadow, shadow-root, mutation, observer |

## Links

- npm: https://www.npmjs.com/package/shadow-observer
- npm.io page: https://npm.io/package/shadow-observer

## Recent versions

- 0.1.7 (latest) — 2026-04-26
- 0.1.6 — 2026-04-26
- 0.1.5 — 2026-04-26
- 0.1.4 — 2026-04-25
- 0.1.3 — 2026-04-24
- 0.1.2 — 2026-04-24
- 0.1.1 — 2026-04-24
- 0.1.0 — 2026-04-24
- 0.0.6 — 2026-04-23
- 0.0.5 — 2026-04-23
- 0.0.4 — 2026-04-23
- 0.0.3 — 2026-04-23
- 0.0.2 — 2026-04-23
- 0.0.1 — 2026-04-23
- 0.0.0 — 2026-04-23

## README

# shadow-observer

<sub><strong>Cover image: AI-generated art (Cursor), 2026. This file is documentation-only and is not covered by the MIT license on the package source code; see <a href="LICENSE">LICENSE</a> for software terms.</strong></sub>

- - -

**TL;DR** - I got tired [in here](https://github.com/whatwg/dom/issues/1287#issuecomment-4305661429), I needed to move forward, enjoy 👋

`MutationObserver` does not cross into shadow trees. This package provides **`ShadowObserver`**, a small subclass that patches `Element.prototype.attachShadow` so that when you observe a node with `subtree: true` and a **`shadow`** mask, matching **author** shadow roots created later under that subtree are observed with the same options.

**Use `ShadowObserver` instead of the global `MutationObserver` everywhere:** shadow forwarding is **strictly opt-in**. Unless `observe` is called with **`subtree: true` and a `shadow` property** set to a supported mask (`true`, `OPEN`, `CLOSED`, or `OPEN | CLOSED`), the class does **not** register any shadow hook for that observation—it just delegates to `MutationObserver.prototype.observe`, so callbacks and options behave like the native observer.

Loading the module still installs one global `attachShadow` wrapper (so the feature can work when you do opt in); with no matching registrations, that wrapper is effectively a no-op aside from a small loop over empty state.

Use it when you control load order and care about **programmatic** `attachShadow` in the **same** JavaScript realm (for example observing `document` or a known app root).

## Usage

Load the module **before** application code that might call `attachShadow` (or keep a reference to the native function), so the patch is in place.

```javascript
import { ShadowObserver, OPEN, CLOSED } from 'shadow-observer';

const observer = new ShadowObserver((records) => {
  // …same as MutationObserver
});

observer.observe(document.documentElement, {
  subtree: true,
  childList: true,
  shadow: OPEN | CLOSED, // follow open and closed author roots
});
```

`subtree` still does **not** reach **into** existing shadow trees from the outer target: each `MutationObserverInit` flag applies per observed root. This package only adds a matching `observe` on **new** programmatic shadow roots, passing the same options there—so for example `attributes: true` affects attribute mutations **on that root’s subtree inside that shadow tree**, not “through” the light-DOM `subtree` from `document` alone.

### `shadow` option

Registration only runs when **`subtree` is true** and the options object **includes the `shadow` key** (even if the value is later ignored).

- **`true`** — same as `OPEN` only.
- **`OPEN`** — forward only when `attachShadow` creates an **open** root (including omitted `mode`, which defaults to `"open"` per the DOM spec).
- **`CLOSED`** — forward only for **closed** roots.
- **`OPEN | CLOSED`** — forward for both open and closed programmatic roots (bitmask of the two exports).

Other values are ignored for shadow forwarding (the normal `observe` call still runs).

### Exports

- **`ShadowObserver`** — extends `MutationObserver`; use `observe` as above.
- **`OPEN`**, **`CLOSED`** — bit flags for the `shadow` option.

## In scope

- Programmatic `Element.attachShadow` on hosts that are **descendants** (in the composed ancestor sense) of the node you passed to `observe`.
- Omitted `ShadowRootInit.mode` treated as `"open"`.
- **Closed author shadow roots** when the `shadow` mask includes `CLOSED` (or `OPEN | CLOSED`). For `mode: "closed"`, `Element.shadowRoot` is `null` for outside code, but this module still `observe`s the `ShadowRoot` return value from inside the `attachShadow` patch—it does not rely on querying the host.

## Out of scope

- **Declarative Shadow DOM** and any shadow tree that is not created through the patched `attachShadow` (no hook runs, so no automatic `observe` on that root).
- **Non-author (user-agent) and other** shadow trees that are never created through author `Element.prototype.attachShadow` in this realm, so no `ShadowRoot` is returned through the patch (open vs. closed is irrelevant to this limitation).
- **Other realms** (e.g. iframes): each frame has its own `Element.prototype`; this patch applies only where the module runs.
- Shadow roots **already attached** before you call `observe` (no retroactive attach event).

## License

MIT

---
_Source: https://npm.io/package/shadow-observer · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
