diff --git a/packages/core/use-size/README.md b/packages/core/use-size/README.md
new file mode 100644
index 000000000..f05912cd7
--- /dev/null
+++ b/packages/core/use-size/README.md
@@ -0,0 +1,10 @@
+# `@oku-ui/use-size`
+
+## Installation
+
+```sh
+$ pnpm add @oku-ui/use-size
+```
+
+## Usage
+...
\ No newline at end of file
diff --git a/packages/core/use-size/package.json b/packages/core/use-size/package.json
new file mode 100644
index 000000000..15e0a0ab1
--- /dev/null
+++ b/packages/core/use-size/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@oku-ui/use-size",
+ "type": "module",
+ "version": "1.0.0",
+ "license": "MIT",
+ "source": "src/index.ts",
+ "funding": "https://github.com/sponsors/productdevbook",
+ "homepage": "https://oku-ui.com/primitives",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/oku-ui/primitives.git"
+ },
+ "bugs": {
+ "url": "https://github.com/oku-ui/primitives/issues"
+ },
+ "exports": {
+ ".": {
+ "types": "./dist/types/index.d.ts",
+ "require": "./dist/use-size.cjs",
+ "import": "./dist/use-size.js"
+ }
+ },
+ "main": "dist/use-size.cjs",
+ "module": "dist/use-size.js",
+ "types": "dist/types/index.d.ts",
+ "files": [
+ "dist",
+ "README.md"
+ ],
+ "scripts": {
+ "clean": "rm -rf dist",
+ "build": "vite build --mode production",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.47"
+ },
+ "devDependencies": {
+ "@types/resize-observer-browser": "^0.1.7",
+ "tsconfig": "workspace:^"
+ }
+}
diff --git a/packages/core/use-size/src/index.ts b/packages/core/use-size/src/index.ts
new file mode 100644
index 000000000..18de6d75b
--- /dev/null
+++ b/packages/core/use-size/src/index.ts
@@ -0,0 +1 @@
+export { useSize } from './use-size'
diff --git a/packages/core/use-size/src/use-size.ts b/packages/core/use-size/src/use-size.ts
new file mode 100644
index 000000000..cd8b957be
--- /dev/null
+++ b/packages/core/use-size/src/use-size.ts
@@ -0,0 +1,75 @@
+///
+
+import type { WatchStopHandle } from 'vue'
+import { onMounted, onUnmounted, ref, watch } from 'vue'
+
+interface Size {
+ width: number
+ height: number
+}
+
+function useSize(element: HTMLElement | null) {
+ const size = ref()
+ let stopHandle: WatchStopHandle
+ let resizeObserver: ResizeObserver
+
+ onMounted(() => {
+ if (element) {
+ size.value = { width: element.offsetWidth, height: element.offsetHeight }
+
+ resizeObserver = new ResizeObserver((entries) => {
+ if (!Array.isArray(entries))
+ return
+
+ // Since we only observe the one element, we don't need to loop over the
+ // array
+ if (!entries.length)
+ return
+
+ const entry = entries[0]
+ let width: number
+ let height: number
+
+ if ('borderBoxSize' in entry) {
+ const borderSizeEntry = entry.borderBoxSize
+ // iron out differences between browsers
+ const borderSize = Array.isArray(borderSizeEntry) ? borderSizeEntry[0] : borderSizeEntry
+ width = borderSize.inlineSize
+ height = borderSize.blockSize
+ }
+ else {
+ // for browsers that don't support `borderBoxSize`
+ // we calculate it ourselves to get the correct border box.
+ width = element.offsetWidth
+ height = element.offsetHeight
+ }
+
+ size.value = { width, height }
+ })
+
+ resizeObserver.observe(element)
+
+ stopHandle = watch(element, (newValue, oldValue) => {
+ if (oldValue)
+ resizeObserver.unobserve(oldValue)
+
+ if (newValue)
+ resizeObserver.observe(newValue)
+ })
+ }
+ else {
+ size.value = undefined
+ }
+ })
+
+ onUnmounted(() => {
+ if (element) {
+ stopHandle()
+ resizeObserver.unobserve(element)
+ }
+ })
+
+ return size
+}
+
+export { useSize }
diff --git a/packages/core/use-size/tsconfig.json b/packages/core/use-size/tsconfig.json
new file mode 100644
index 000000000..01616135e
--- /dev/null
+++ b/packages/core/use-size/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./../../../tsconfig.json",
+ "compilerOptions": {
+ "baseUrl": "."
+ },
+ "include": [
+ "./"
+ ]
+}
diff --git a/packages/core/use-size/vite.config.ts b/packages/core/use-size/vite.config.ts
new file mode 100644
index 000000000..206f3258c
--- /dev/null
+++ b/packages/core/use-size/vite.config.ts
@@ -0,0 +1,48 @@
+import path, { resolve } from 'node:path'
+
+import Vue from '@vitejs/plugin-vue'
+import { defineConfig } from 'vite'
+
+// https://github.com/qmhc/vite-plugin-dts
+import dtsPlugin from 'vite-plugin-dts'
+
+// https://github.com/sxzz/unplugin-vue-macros
+import VueMacros from 'unplugin-vue-macros/vite'
+
+import * as pkg from './package.json'
+
+const externals = [
+ ...Object.keys(pkg.peerDependencies || {}),
+ ...Object.keys(pkg.dependencies || {}),
+]
+export default defineConfig({
+ plugins: [
+ dtsPlugin({
+ include: ['./src/**/*.ts', './src/**/*.tsx', './src/**/*.vue'],
+ skipDiagnostics: false,
+ staticImport: true,
+ outputDir: ['./dist/types'],
+ cleanVueFileName: false,
+ }),
+ VueMacros({
+ plugins: {
+ vue: Vue(),
+ },
+ }),
+ ],
+ resolve: {
+ alias: {
+ '@': resolve(__dirname, 'src'),
+ },
+ },
+ build: {
+ target: 'modules',
+ lib: {
+ entry: path.resolve(__dirname, './src/index.ts'),
+ formats: ['es', 'cjs'],
+ },
+ rollupOptions: {
+ external: externals,
+ },
+ },
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c463926dd..2526eb3df 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -127,6 +127,16 @@ importers:
specifier: ^3.3.0-beta.3
version: 3.3.0-beta.3
+ packages/core/compose-refs:
+ dependencies:
+ vue:
+ specifier: ^3.2.47
+ version: 3.2.47
+ devDependencies:
+ tsconfig:
+ specifier: workspace:^
+ version: link:../../tsconfig
+
packages/core/primitive:
dependencies:
vue:
@@ -170,6 +180,25 @@ importers:
specifier: workspace:^
version: link:../../tsconfig
+ packages/core/use-previous:
+ dependencies:
+ vue:
+ specifier: ^3.2.47
+ version: 3.2.47
+ devDependencies:
+ tsconfig:
+ specifier: workspace:^
+ version: link:../../tsconfig
+
+ packages/core/use-size:
+ devDependencies:
+ '@types/resize-observer-browser':
+ specifier: ^0.1.7
+ version: 0.1.7
+ tsconfig:
+ specifier: workspace:^
+ version: link:../../tsconfig
+
packages/eslint-config-custom:
dependencies:
'@antfu/eslint-config':
@@ -3715,6 +3744,10 @@ packages:
csstype: 3.1.2
dev: true
+ /@types/resize-observer-browser@0.1.7:
+ resolution: {integrity: sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg==}
+ dev: true
+
/@types/resolve@1.20.2:
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
dev: true