diff --git a/packages/docs/src/en/plugins/anchor.md b/packages/docs/src/en/plugins/anchor.md
index 05cad10d1..b7c3b94c7 100644
--- a/packages/docs/src/en/plugins/anchor.md
+++ b/packages/docs/src/en/plugins/anchor.md
@@ -1,5 +1,5 @@
---
-order: 5
+order: 7
title: Anchor
description: Anchor an element's positioning to another element on the page
graph_image: https://alpinejs.dev/social_anchor.jpg
diff --git a/packages/docs/src/en/plugins/collapse.md b/packages/docs/src/en/plugins/collapse.md
index de9472f8c..063caec54 100644
--- a/packages/docs/src/en/plugins/collapse.md
+++ b/packages/docs/src/en/plugins/collapse.md
@@ -1,5 +1,5 @@
---
-order: 4
+order: 6
title: Collapse
description: Collapse and expand elements with robust animations
graph_image: https://alpinejs.dev/social_collapse.jpg
diff --git a/packages/docs/src/en/plugins/focus.md b/packages/docs/src/en/plugins/focus.md
index 57c9f92fc..90cc728d3 100644
--- a/packages/docs/src/en/plugins/focus.md
+++ b/packages/docs/src/en/plugins/focus.md
@@ -1,5 +1,5 @@
---
-order: 3
+order: 5
title: Focus
description: Easily manage focus within the page
graph_image: https://alpinejs.dev/social_focus.jpg
diff --git a/packages/docs/src/en/plugins/morph.md b/packages/docs/src/en/plugins/morph.md
index b8a8769af..cc60d4a79 100644
--- a/packages/docs/src/en/plugins/morph.md
+++ b/packages/docs/src/en/plugins/morph.md
@@ -1,5 +1,5 @@
---
-order: 6
+order: 8
title: Morph
description: Morph an element into the provided HTML
graph_image: https://alpinejs.dev/social_morph.jpg
diff --git a/packages/docs/src/en/plugins/persist.md b/packages/docs/src/en/plugins/persist.md
index f8850194c..4c6fb665a 100644
--- a/packages/docs/src/en/plugins/persist.md
+++ b/packages/docs/src/en/plugins/persist.md
@@ -1,5 +1,5 @@
---
-order: 2
+order: 4
title: Persist
description: Easily persist data across page loads using localStorage
graph_image: https://alpinejs.dev/social_persist.jpg
diff --git a/packages/docs/src/en/plugins/resize.md b/packages/docs/src/en/plugins/resize.md
new file mode 100644
index 000000000..cf9b27e6c
--- /dev/null
+++ b/packages/docs/src/en/plugins/resize.md
@@ -0,0 +1,99 @@
+---
+order: 3
+title: Resize
+description: An Alpine convenience wrapper for the Resize Observer API that allows you to easily react when an element is resized.
+graph_image: https://alpinejs.dev/social_resize.jpg
+---
+
+# Resize Plugin
+
+Alpine's Resize plugin is a convenience wrapper for the [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API) that allows you to easily react when an element changes size.
+
+This is useful for: custom size-based animations, intelligent sticky positioning, conditionally adding attributes based on the element's size, etc.
+
+
+## Installation
+
+You can use this plugin by either including it from a `
+
+
+
+```
+
+### Via NPM
+
+You can install Resize from NPM for use inside your bundle like so:
+
+```shell
+npm install @alpinejs/resize
+```
+
+Then initialize it from your bundle:
+
+```js
+import Alpine from 'alpinejs'
+import resize from '@alpinejs/resize'
+
+Alpine.plugin(resize)
+
+...
+```
+
+
+## x-resize
+
+The primary API for using this plugin is `x-resize`. You can add `x-resize` to any element within an Alpine component, and when that element is resized for any reason, the provided expression will execute with two magic properties: `$width` and `$height`.
+
+For example, here's a simple example of using `x-resize` do display the width and height of an element as it changes size.
+
+```alpine
+
+
+
+
+```
+
+
+
+
+ Resize your browser window to see the width and height values change.
+
+
+
+
+
+
+
+
+## Modifiers
+
+
+### .document
+
+It's often useful to observer the entire document's size, rather than a specific element. To do this, you can add the `.document` modifier to `x-resize`:
+
+```alpine
+
+```
+
+
+
+
+ Resize your browser window to see the document width and height values change.
+
+
+
+
+
+
diff --git a/packages/docs/src/en/plugins/sort.md b/packages/docs/src/en/plugins/sort.md
index 33ef2217a..3595c9173 100644
--- a/packages/docs/src/en/plugins/sort.md
+++ b/packages/docs/src/en/plugins/sort.md
@@ -1,5 +1,5 @@
---
-order: 6
+order: 9
title: Sort
description: Easily re-order elements by dragging them with your mouse
graph_image: https://alpinejs.dev/social_sort.jpg
diff --git a/packages/resize/builds/cdn.js b/packages/resize/builds/cdn.js
new file mode 100644
index 000000000..b8a3f5b76
--- /dev/null
+++ b/packages/resize/builds/cdn.js
@@ -0,0 +1,5 @@
+import resize from '../src/index.js'
+
+document.addEventListener('alpine:init', () => {
+ window.Alpine.plugin(resize)
+})
diff --git a/packages/resize/builds/module.js b/packages/resize/builds/module.js
new file mode 100644
index 000000000..fc370a076
--- /dev/null
+++ b/packages/resize/builds/module.js
@@ -0,0 +1,5 @@
+import resize from './../src/index.js'
+
+export default resize
+
+export { resize }
diff --git a/packages/resize/package.json b/packages/resize/package.json
new file mode 100644
index 000000000..f0dd6d313
--- /dev/null
+++ b/packages/resize/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "@alpinejs/resize",
+ "version": "3.14.1",
+ "description": "Trigger JavaScript when an element is resized on the page",
+ "homepage": "https://alpinejs.dev/plugins/intersect",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/alpinejs/alpine.git",
+ "directory": "packages/resize"
+ },
+ "author": "Caleb Porzio",
+ "license": "MIT",
+ "main": "dist/module.cjs.js",
+ "module": "dist/module.esm.js",
+ "unpkg": "dist/cdn.min.js",
+ "dependencies": {}
+}
diff --git a/packages/resize/src/index.js b/packages/resize/src/index.js
new file mode 100644
index 000000000..24d647595
--- /dev/null
+++ b/packages/resize/src/index.js
@@ -0,0 +1,59 @@
+export default function (Alpine) {
+ Alpine.directive('resize', Alpine.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => {
+ let evaluator = evaluateLater(expression)
+
+ let evaluate = (width, height) => {
+ evaluator(() => {}, { scope: { '$width': width, '$height': height }})
+ }
+
+ let off = modifiers.includes('document')
+ ? onDocumentResize(evaluate)
+ : onElResize(el, evaluate)
+
+ cleanup(() => off())
+ }))
+}
+
+function onElResize(el, callback) {
+ let observer = new ResizeObserver((entries) => {
+ let [width, height] = dimensions(entries)
+
+ callback(width, height)
+ })
+
+ observer.observe(el)
+
+ return () => observer.disconnect()
+}
+
+let documentResizeObserver
+let documentResizeObserverCallbacks = new Set
+
+function onDocumentResize(callback) {
+ documentResizeObserverCallbacks.add(callback)
+
+ if (! documentResizeObserver) {
+ documentResizeObserver = new ResizeObserver((entries) => {
+ let [width, height] = dimensions(entries)
+
+ documentResizeObserverCallbacks.forEach(i => i(width, height))
+ })
+
+ documentResizeObserver.observe(document.documentElement)
+ }
+
+ return () => {
+ documentResizeObserverCallbacks.delete(callback)
+ }
+}
+
+function dimensions(entries) {
+ let width, height
+
+ for (let entry of entries) {
+ width = entry.borderBoxSize[0].inlineSize
+ height = entry.borderBoxSize[0].blockSize
+ }
+
+ return [width, height]
+}
diff --git a/scripts/build.js b/scripts/build.js
index e02d07733..c7421ab44 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -9,8 +9,9 @@ let zlib = require('zlib');
// 'history', - removed because this plugin has been moved to livewire/livewire until it's stable...
// 'navigate', - remove because this plugin has been moved to livewire/livewire until it's stable...
'intersect',
- 'persist',
'collapse',
+ 'persist',
+ 'resize',
'anchor',
'morph',
'focus',
diff --git a/scripts/release.js b/scripts/release.js
index a9624d1e0..a94624aaa 100644
--- a/scripts/release.js
+++ b/scripts/release.js
@@ -45,6 +45,9 @@ function writeNewAlpineVersion() {
writeToPackageDotJson('intersect', 'version', version)
console.log('Bumping @alpinejs/intersect package.json: '+version)
+ writeToPackageDotJson('resize', 'version', version)
+ console.log('Bumping @alpinejs/resize package.json: '+version)
+
writeToPackageDotJson('persist', 'version', version)
console.log('Bumping @alpinejs/persist package.json: '+version)
@@ -92,6 +95,9 @@ function publish() {
console.log('Publishing @alpinejs/intersect on NPM...');
runFromPackage('intersect', 'npm publish --access public')
+ console.log('Publishing @alpinejs/resize on NPM...');
+ runFromPackage('resize', 'npm publish --access public')
+
console.log('Publishing @alpinejs/persist on NPM...');
runFromPackage('persist', 'npm publish --access public')
diff --git a/tests/cypress/integration/plugins/resize.spec.js b/tests/cypress/integration/plugins/resize.spec.js
new file mode 100644
index 000000000..6004ef31b
--- /dev/null
+++ b/tests/cypress/integration/plugins/resize.spec.js
@@ -0,0 +1,48 @@
+import { haveText, test, html, notHaveText } from '../../utils'
+
+test('can react to the resizing of an element',
+ [html`
+
+
+
+
+
+
+
+
+
+
+ `],
+ ({ get }) => {
+ get('h1').should(haveText('100'))
+ get('h2').should(haveText('100'))
+ get('button#1').click()
+ get('h1').should(haveText('50'))
+ get('h2').should(haveText('100'))
+ get('button#2').click()
+ get('h1').should(haveText('50'))
+ get('h2').should(haveText('50'))
+ },
+)
+
+test('can react to the resizing of the document',
+ [html`
+