diff --git a/documentation/docs/02-runes/02-$state.md b/documentation/docs/02-runes/02-$state.md
index 49e17cd08f..e05590ce61 100644
--- a/documentation/docs/02-runes/02-$state.md
+++ b/documentation/docs/02-runes/02-$state.md
@@ -164,6 +164,47 @@ To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snaps
This is handy when you want to pass some state to an external library or API that doesn't expect a proxy, such as `structuredClone`.
+## `$state.invalidate`
+
+In the case that you aren't using a proxied `$state` via use of `$state.raw` or a class instance, you may need to tell Svelte a `$state` has changed. You can do so via `$state.invalidate`:
+
+```svelte
+
+
+```
+
+`$state.invalidate` can also be used with reactive class fields:
+
+```js
+class Box {
+ value;
+
+ constructor(initial) {
+ this.value = initial;
+ }
+}
+
+class Counter {
+ count = $state(new Box(0));
+
+ increment() {
+ this.count.value++;
+ $state.invalidate(this.count);
+ }
+}
+```
+
## Passing state into functions
JavaScript is a _pass-by-value_ language — when you call a function, the arguments are the _values_ rather than the _variables_. In other words:
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 36189cb749..3bb8a2e4cf 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -215,6 +215,15 @@ export function internal_set(source, value) {
* @param {Source} source
*/
export function invalidate(source) {
+ if (
+ active_reaction !== null &&
+ !untracking &&
+ is_runes() &&
+ (active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 &&
+ !reaction_sources?.includes(source)
+ ) {
+ e.state_unsafe_mutation();
+ }
source.wv = increment_write_version();
mark_reactions(source, DIRTY);