Repository: melt-ui/melt-ui
Pull request #1052 (2024-03-04 14:10:20)

refactor listbox
Listbox Refactor:
- setting `activeTrigger` in trigger action instead of in `openMenu` and in `safeOnMount` and in `effect([open])`
- using early return in `effect([open])` for cleaner code

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: f97bd87e1b3f15adfd6ce2d0a6d8a7a670cc1aa5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/anatolzak/melt-ui/new/refactor/listbox?filename=.changeset/red-falcons-mix.md&value=---%0A%22%40melt-ui%2Fsvelte%22%3A%20patch%0A---%0A%0Arefactor%20listbox%0A)


huntabyte: Same comments as #1055 

Patch:
diff --git a/.changeset/khaki-files-try.md b/.changeset/khaki-files-try.md
new file mode 100644
index 00000000..39b1cd66
--- /dev/null
+++ b/.changeset/khaki-files-try.md
@@ -0,0 +1,5 @@
+---
+'@melt-ui/svelte': patch
+---
+
+Enhanced type accuracy by implementing as const for builder props, ensuring more precise type inference.
diff --git a/.changeset/many-rockets-cough.md b/.changeset/many-rockets-cough.md
deleted file mode 100644
index ef373a82..00000000
--- a/.changeset/many-rockets-cough.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-Fixed bug where focus trap deactivates in dialog and popover when pressing escape provided `closeOnEscape: false` (closes #1091)
diff --git a/.changeset/silly-eels-search.md b/.changeset/silly-eels-search.md
deleted file mode 100644
index 01c56cb6..00000000
--- a/.changeset/silly-eels-search.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@melt-ui/svelte": patch
----
-
-performance: avoid effect multiple runs
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 8fc78646..a0488854 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -14,7 +14,7 @@ concurrency:
 
 permissions:
   contents: read # to fetch code (actions/checkout)
-  
+
 jobs:
   Lint:
     runs-on: ubuntu-latest
@@ -35,7 +35,6 @@ jobs:
           version: 8.6.3
           run_install: true
       - run: GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} && pnpm run check
-      
 
   Tests:
     runs-on: ubuntu-latest
@@ -46,4 +45,16 @@ jobs:
           version: 8.6.3
           run_install: true
       - run: GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} && pnpm run test
-      
\ No newline at end of file
+
+  Playwright-Tests:
+    name: Playwright Tests
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: pnpm/action-setup@v2
+        with:
+          version: 8.6.3
+          run_install: true
+      - name: Install Playwright Browsers
+        run: pnpm exec playwright install --with-deps
+      - run: GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} && pnpm run pw
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96bb9e73..dfa35dc6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
 # @melt-ui/svelte
 
+## 0.76.2
+
+### Patch Changes
+
+- Fixed a bug on touch devices where an outside interaction leaked click events to other dom elements (closes #1115) ([#1118](https://github.com/melt-ui/melt-ui/pull/1118))
+
+## 0.76.1
+
+### Patch Changes
+
+- Fixed bug where focus trap deactivates in dialog and popover when clicking outside provided `closeOnOutsideClick: false` (closes #1084) ([#1094](https://github.com/melt-ui/melt-ui/pull/1094))
+
+- Combobox: menu no longer closes when composing (Closes: #1106) ([#1107](https://github.com/melt-ui/melt-ui/pull/1107))
+
+- Fixed bug where focus trap deactivates in dialog and popover when pressing escape provided `closeOnEscape: false` (closes #1091) ([#1095](https://github.com/melt-ui/melt-ui/pull/1095))
+
+- Fixed memory leak caused by race conditions for various components (closes #1086) ([#1087](https://github.com/melt-ui/melt-ui/pull/1087))
+
+- performance: avoid effect multiple runs ([#1089](https://github.com/melt-ui/melt-ui/pull/1089))
+
+- Listbox: correct `arrowSize` JSDoc defaultValue ([#1050](https://github.com/melt-ui/melt-ui/pull/1050))
+
+- Popover: fixed a bug that has the potential to introduce a memory leak via the overlay ([#1112](https://github.com/melt-ui/melt-ui/pull/1112))
+
 ## 0.76.0
 
 ### Minor Changes
diff --git a/package.json b/package.json
index 046b25e9..e79589bc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@melt-ui/svelte",
-	"version": "0.76.0",
+	"version": "0.76.2",
 	"license": "MIT",
 	"repository": "github:melt-ui/melt-ui",
 	"author": "Thomas G. Lopes",
diff --git a/playwright.config.ts b/playwright.config.ts
index 07994026..51ef844b 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -35,17 +35,17 @@ export default defineConfig({
 	projects: [
 		{
 			name: 'chromium',
-			use: { ...devices['Desktop Chrome'] },
+			use: { ...devices['Desktop Chrome'], hasTouch: true },
 		},
 
 		{
 			name: 'firefox',
-			use: { ...devices['Desktop Firefox'] },
+			use: { ...devices['Desktop Firefox'], hasTouch: true },
 		},
 
 		{
 			name: 'webkit',
-			use: { ...devices['Desktop Safari'] },
+			use: { ...devices['Desktop Safari'], hasTouch: true },
 		},
 
 		/* Test against mobile viewports. */
diff --git a/src/docs/components/nav/mobile-nav.svelte b/src/docs/components/nav/mobile-nav.svelte
index 8581a9e5..9698413a 100644
--- a/src/docs/components/nav/mobile-nav.svelte
+++ b/src/docs/components/nav/mobile-nav.svelte
@@ -25,8 +25,8 @@
 	<Menu class="size-6" />
 	<span class="sr-only">Toggle Menu</span>
 </button>
-<div use:melt={$portalled} class="md:hidden">
-	{#if $open}
+{#if $open}
+	<div use:melt={$portalled} class="md:hidden">
 		<div
 			use:melt={$overlay}
 			class="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm"
@@ -86,8 +86,8 @@
 				</div>
 			</div>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
 
 <style lang="postcss">
 	.menu,
diff --git a/src/docs/components/tooltip.svelte b/src/docs/components/tooltip.svelte
index e381cca6..29fc37d8 100644
--- a/src/docs/components/tooltip.svelte
+++ b/src/docs/components/tooltip.svelte
@@ -1,6 +1,6 @@
 <script lang="ts">
-	import { browser } from '$app/environment';
 	import { createTooltip, melt } from '$lib/index.js';
+	import { fade } from 'svelte/transition';
 
 	const {
 		elements: { trigger, content, arrow },
@@ -20,30 +20,20 @@
 	<slot />
 </div>
 
-<div
-	use:melt={$content}
-	class="z-50 rounded-md bg-neutral-700 px-2 py-1 text-sm text-neutral-50 shadow-sm"
-	data-open={$open ? '' : undefined}
-	class:hidden={!browser}
->
-	<div use:melt={$arrow} />
-	{text}
-</div>
+{#if $open}
+	<div
+		use:melt={$content}
+		in:fade={{ duration: 150 }}
+		class="z-50 rounded-md bg-neutral-700 px-2 py-1 text-sm text-neutral-50 shadow-sm"
+	>
+		<div use:melt={$arrow} />
+		{text}
+	</div>
+{/if}
 
 <style lang="postcss">
 	[data-melt-tooltip-trigger] {
 		display: grid;
 		place-items: center;
 	}
-
-	[data-melt-tooltip-content] {
-		opacity: 0;
-		visibility: hidden;
-		transition: opacity 150ms ease;
-
-		&[data-open] {
-			opacity: 1;
-			visibility: visible;
-		}
-	}
 </style>
diff --git a/src/docs/content/builders/dialog.md b/src/docs/content/builders/dialog.md
index 4e406287..a1d5e168 100644
--- a/src/docs/content/builders/dialog.md
+++ b/src/docs/content/builders/dialog.md
@@ -28,16 +28,16 @@ At a high level, the anatomy of a dialog looks like this:
 
 <button use:melt={$trigger}> Open Dialog </button>
 
-<div use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div use:melt={$portalled}>
 		<div use:melt={$overlay} />
 		<div use:melt={$content}>
 			<h2 use:melt={$title}>Dialog Title</h2>
 			<p use:melt={$description}>Dialog description</p>
 			<button use:melt={$close}> Close Dialog </button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
 ```
 
 - **Trigger**: The button(s) that open the dialog
diff --git a/src/docs/previews/dialog/alert/css/index.svelte b/src/docs/previews/dialog/alert/css/index.svelte
index a420fcf1..871b2204 100644
--- a/src/docs/previews/dialog/alert/css/index.svelte
+++ b/src/docs/previews/dialog/alert/css/index.svelte
@@ -22,8 +22,8 @@
 </script>
 
 <button use:melt={$trigger} class="trigger"> Delete Item </button>
-<div use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div use:melt={$portalled}>
 		<div use:melt={$overlay} class="overlay" />
 		<div
 			class="content"
@@ -51,8 +51,8 @@
 				<X class="size-4" />
 			</button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
 
 <style>
 	.trigger {
diff --git a/src/docs/previews/dialog/alert/tailwind/index.svelte b/src/docs/previews/dialog/alert/tailwind/index.svelte
index e2d0c132..442b16f3 100644
--- a/src/docs/previews/dialog/alert/tailwind/index.svelte
+++ b/src/docs/previews/dialog/alert/tailwind/index.svelte
@@ -29,8 +29,8 @@
 	Delete Item
 </button>
 
-<div class="force-dark" use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div class="force-dark" use:melt={$portalled}>
 		<div use:melt={$overlay} class="fixed inset-0 z-50 bg-black/50" />
 		<div
 			class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw]
@@ -78,5 +78,5 @@
 				<X class="size-4" />
 			</button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
diff --git a/src/docs/previews/dialog/controlled/css/index.svelte b/src/docs/previews/dialog/controlled/css/index.svelte
index 67dd5241..289b8a5e 100644
--- a/src/docs/previews/dialog/controlled/css/index.svelte
+++ b/src/docs/previews/dialog/controlled/css/index.svelte
@@ -25,8 +25,8 @@
 </script>
 
 <button use:melt={$trigger} class="trigger"> Open Dialog </button>
-<div use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div use:melt={$portalled}>
 		<div use:melt={$overlay} class="overlay" />
 		<div
 			class="content"
@@ -59,8 +59,8 @@
 				<X class="size-4" />
 			</button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
 
 <style lang="postcss">
 	.trigger {
diff --git a/src/docs/previews/dialog/controlled/tailwind/index.svelte b/src/docs/previews/dialog/controlled/tailwind/index.svelte
index 5b9c1bec..fa0dfdd7 100644
--- a/src/docs/previews/dialog/controlled/tailwind/index.svelte
+++ b/src/docs/previews/dialog/controlled/tailwind/index.svelte
@@ -31,8 +31,8 @@
 >
 	Open Dialog
 </button>
-<div class="force-dark" use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div class="force-dark" use:melt={$portalled}>
 		<div use:melt={$overlay} class="fixed inset-0 z-50 bg-black/50" />
 		<div
 			class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw]
@@ -101,5 +101,5 @@
 				<X class="size-4" />
 			</button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
diff --git a/src/docs/previews/dialog/drawer/css/index.svelte b/src/docs/previews/dialog/drawer/css/index.svelte
index d03a33d7..05cc5385 100644
--- a/src/docs/previews/dialog/drawer/css/index.svelte
+++ b/src/docs/previews/dialog/drawer/css/index.svelte
@@ -21,8 +21,8 @@
 </script>
 
 <button use:melt={$trigger} class="trigger"> View Notifications </button>
-<div use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div use:melt={$portalled}>
 		<div
 			use:melt={$overlay}
 			class="overlay"
@@ -57,8 +57,8 @@
 				</div>
 			</section>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
 
 <style>
 	.trigger {
diff --git a/src/docs/previews/dialog/drawer/tailwind/index.svelte b/src/docs/previews/dialog/drawer/tailwind/index.svelte
index 916af375..b516de07 100644
--- a/src/docs/previews/dialog/drawer/tailwind/index.svelte
+++ b/src/docs/previews/dialog/drawer/tailwind/index.svelte
@@ -28,8 +28,8 @@
 >
 	View Notifications
 </button>
-<div class="force-dark" use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div class="force-dark" use:melt={$portalled}>
 		<div
 			use:melt={$overlay}
 			class="fixed inset-0 z-50 bg-black/50"
@@ -88,5 +88,5 @@
 				</div>
 			</section>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
diff --git a/src/docs/previews/dialog/main/css/index.svelte b/src/docs/previews/dialog/main/css/index.svelte
index e9df12ab..dee12837 100644
--- a/src/docs/previews/dialog/main/css/index.svelte
+++ b/src/docs/previews/dialog/main/css/index.svelte
@@ -19,8 +19,8 @@
 </script>
 
 <button use:melt={$trigger} class="trigger"> Open Dialog </button>
-<div use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div use:melt={$portalled}>
 		<div use:melt={$overlay} class="overlay" />
 		<div
 			class="content"
@@ -53,8 +53,8 @@
 				<X class="size-4" />
 			</button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
 
 <style lang="postcss">
 	.trigger {
diff --git a/src/docs/previews/dialog/main/tailwind/index.svelte b/src/docs/previews/dialog/main/tailwind/index.svelte
index add998df..a64e1d1c 100644
--- a/src/docs/previews/dialog/main/tailwind/index.svelte
+++ b/src/docs/previews/dialog/main/tailwind/index.svelte
@@ -29,8 +29,8 @@
 	Open Dialog
 </button>
 
-<div class="force-dark" use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div class="force-dark" use:melt={$portalled}>
 		<div
 			use:melt={$overlay}
 			class="fixed inset-0 z-50 bg-black/50"
@@ -100,5 +100,5 @@
 				<X class="size-4" />
 			</button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
diff --git a/src/docs/previews/dialog/nested/css/index.svelte b/src/docs/previews/dialog/nested/css/index.svelte
index 1e753b0d..88a82051 100644
--- a/src/docs/previews/dialog/nested/css/index.svelte
+++ b/src/docs/previews/dialog/nested/css/index.svelte
@@ -36,8 +36,8 @@
 </script>
 
 <button use:melt={$trigger} class="trigger"> Open Dialog </button>
-<div use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div use:melt={$portalled}>
 		<div use:melt={$overlay} class="overlay" />
 		<div
 			class="content"
@@ -62,11 +62,11 @@
 				<X />
 			</button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
 
-<div use:melt={$portalledNested}>
-	{#if $openNested}
+{#if $openNested}
+	<div use:melt={$portalledNested}>
 		<div use:melt={$overlayNested} class="overlay overlay-nested" />
 		<div
 			class="content content-nested"
@@ -90,8 +90,8 @@
 				<X />
 			</button>
 		</div>
-	{/if}
-</div>
+	</div>
+{/if}
 
 <style>
 	.trigger {
diff --git a/src/docs/previews/dialog/nested/tailwind/index.svelte b/src/docs/previews/dialog/nested/tailwind/index.svelte
index 83a28825..852b639a 100644
--- a/src/docs/previews/dialog/nested/tailwind/index.svelte
+++ b/src/docs/previews/dialog/nested/tailwind/index.svelte
@@ -38,8 +38,8 @@
 >
 	Open Dialog
 </button>
-<div class="force-dark" use:melt={$portalled}>
-	{#if $open}
+{#if $open}
+	<div class="force-dark" use:melt={$portalled}>
 		<div use:melt={$overlay} class="fixed inset-0 z-50 bg-black/50" />
 		<div
 			class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw]
@@ -75,8 +75,8 @@
 					Open second
 				</button>
 			</div>
-			<div class="force-dark" use:melt={$portalledNested}>
-				{#if $openNested}
+			{#if $openNested}
+				<div class="force-dark" use:melt={$portalledNested}>
 					<div
 						use:melt={$overlayNested}
 						class="fixed inset-0 z-50 bg-black/75"
@@ -124,8 +124,8 @@
 							<X class="size-4" />
 						</button>
 					</div>
-				{/if}
-			</div>
+				</div>
+			{/if}
 		</div>
 		<button
 			use:melt={$close}
@@ -135,5 +135,5 @@
 		>
 			<X />
 		</button>
-	{/if}
-</div>
+	</div>
+{/if}
diff --git a/src/lib/builders/accordion/create.ts b/src/lib/builders/accordion/create.ts
index 3fc78ee2..ddad700b 100644
--- a/src/lib/builders/accordion/create.ts
+++ b/src/lib/builders/accordion/create.ts
@@ -59,9 +59,10 @@ export const createAccordion = <Multiple extends boolean = false>(
 	});
 
 	const root = makeElement(name(), {
-		returned: () => ({
-			'data-melt-id': meltIds.root,
-		}),
+		returned: () =>
+			({
+				'data-melt-id': meltIds.root,
+			} as const),
 	});
 
 	const parseItemProps = (props: AccordionItemProps) => {
@@ -89,7 +90,7 @@ export const createAccordion = <Multiple extends boolean = false>(
 				return {
 					'data-state': isSelected(itemValue, $value) ? 'open' : 'closed',
 					'data-disabled': disabledAttr(disabled),
-				};
+				} as const;
 			};
 		},
 	});
@@ -108,7 +109,7 @@ export const createAccordion = <Multiple extends boolean = false>(
 					'data-disabled': disabledAttr(disabled),
 					'data-value': itemValue,
 					'data-state': isSelected(itemValue, $value) ? 'open' : 'closed',
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<AccordionEvents['trigger']> => {
@@ -182,7 +183,7 @@ export const createAccordion = <Multiple extends boolean = false>(
 					style: styleToString({
 						display: isVisible ? undefined : 'none',
 					}),
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement) => {
@@ -210,7 +211,7 @@ export const createAccordion = <Multiple extends boolean = false>(
 					role: 'heading',
 					'aria-level': level,
 					'data-heading-level': level,
-				};
+				} as const;
 			};
 		},
 	});
diff --git a/src/lib/builders/avatar/create.ts b/src/lib/builders/avatar/create.ts
index 14be4549..3d2389a0 100644
--- a/src/lib/builders/avatar/create.ts
+++ b/src/lib/builders/avatar/create.ts
@@ -54,7 +54,7 @@ export const createAvatar = (props?: CreateAvatarProps) => {
 			return {
 				src: $src,
 				style: imageStyles,
-			};
+			} as const;
 		},
 	});
 
@@ -69,7 +69,7 @@ export const createAvatar = (props?: CreateAvatarProps) => {
 						  })
 						: undefined,
 				hidden: $loadingStatus === 'loaded' ? true : undefined,
-			};
+			} as const;
 		},
 	});
 
diff --git a/src/lib/builders/calendar/create.ts b/src/lib/builders/calendar/create.ts
index 1987b1ba..0f6fd1bd 100644
--- a/src/lib/builders/calendar/create.ts
+++ b/src/lib/builders/calendar/create.ts
@@ -286,7 +286,7 @@ export function createCalendar<
 				'data-invalid': $isInvalid ? '' : undefined,
 				'data-disabled': $disabled ? '' : undefined,
 				'data-readonly': $readonly ? '' : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<CalendarEvents['calendar']> => {
 			/**
@@ -323,7 +323,7 @@ export function createCalendar<
 			return {
 				'aria-hidden': true,
 				'data-disabled': $disabled ? '' : undefined,
-			};
+			} as const;
 		},
 	});
 
@@ -340,11 +340,11 @@ export function createCalendar<
 			return {
 				tabindex: -1,
 				role: 'grid',
-				'aria-readonly': $readonly ? ('true' as const) : undefined,
-				'aria-disabled': $disabled ? ('true' as const) : undefined,
+				'aria-readonly': $readonly ? 'true' : undefined,
+				'aria-disabled': $disabled ? 'true' : undefined,
 				'data-readonly': $readonly ? '' : undefined,
 				'data-disabled': $disabled ? '' : undefined,
-			};
+			} as const;
 		},
 	});
 
@@ -362,10 +362,10 @@ export function createCalendar<
 				role: 'button',
 				type: 'button' as const,
 				'aria-label': 'Previous',
-				'aria-disabled': disabled ? ('true' as const) : undefined,
+				'aria-disabled': disabled ? 'true' : undefined,
 				'data-disabled': disabled ? '' : undefined,
 				disabled: disabled ? true : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<CalendarEvents['prevButton']> => {
 			const unsub = executeCallbacks(
@@ -394,10 +394,10 @@ export function createCalendar<
 				role: 'button',
 				type: 'button' as const,
 				'aria-label': 'Next',
-				'aria-disabled': disabled ? ('true' as const) : undefined,
+				'aria-disabled': disabled ? 'true' : undefined,
 				'data-disabled': disabled ? '' : undefined,
 				disabled: disabled ? true : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<CalendarEvents['nextButton']> => {
 			const unsub = executeCallbacks(
diff --git a/src/lib/builders/checkbox/create.ts b/src/lib/builders/checkbox/create.ts
index 3e33ba85..b139d4f1 100644
--- a/src/lib/builders/checkbox/create.ts
+++ b/src/lib/builders/checkbox/create.ts
@@ -72,7 +72,7 @@ export function createCheckbox(props?: CreateCheckboxProps) {
 		stores: [checked, name, value, required, disabled],
 		returned: ([$checked, $name, $value, $required, $disabled]) => {
 			return {
-				type: 'checkbox' as const,
+				type: 'checkbox',
 				'aria-hidden': true,
 				hidden: true,
 				tabindex: -1,
diff --git a/src/lib/builders/collapsible/create.ts b/src/lib/builders/collapsible/create.ts
index 7c0a264b..bd8022bf 100644
--- a/src/lib/builders/collapsible/create.ts
+++ b/src/lib/builders/collapsible/create.ts
@@ -32,10 +32,11 @@ export function createCollapsible(props?: CreateCollapsibleProps) {
 
 	const root = makeElement(name(), {
 		stores: [open, disabled],
-		returned: ([$open, $disabled]) => ({
-			'data-state': $open ? 'open' : 'closed',
-			'data-disabled': disabledAttr($disabled),
-		}),
+		returned: ([$open, $disabled]) =>
+			({
+				'data-state': $open ? 'open' : 'closed',
+				'data-disabled': disabledAttr($disabled),
+			} as const),
 	});
 
 	const trigger = makeElement(name('trigger'), {
@@ -72,7 +73,7 @@ export function createCollapsible(props?: CreateCollapsibleProps) {
 			hidden: $isVisible ? undefined : true,
 			style: styleToString({
 				display: $isVisible ? undefined : 'none',
-			}),
+			} as const),
 		}),
 	});
 
diff --git a/src/lib/builders/context-menu/create.ts b/src/lib/builders/context-menu/create.ts
index 11099d65..7365de14 100644
--- a/src/lib/builders/context-menu/create.ts
+++ b/src/lib/builders/context-menu/create.ts
@@ -159,9 +159,10 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 					unsubPopper();
 					if (!$isVisible || !$rootActiveTrigger) return;
 					tick().then(() => {
+						unsubPopper();
 						setMeltMenuAttribute(node, selector);
 						const $virtual = virtual.get();
-						const popper = usePopper(node, {
+						unsubPopper = usePopper(node, {
 							anchorElement: $virtual ? $virtual : $rootActiveTrigger,
 							open: rootOpen,
 							options: {
@@ -177,9 +178,7 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 								portal: getPortalDestination(node, $portal),
 								escapeKeydown: $closeOnEscape ? undefined : null,
 							},
-						});
-						if (!popper || !popper.destroy) return;
-						unsubPopper = popper.destroy;
+						}).destroy;
 					});
 				}
 			);
diff --git a/src/lib/builders/date-field/create.ts b/src/lib/builders/date-field/create.ts
index 7f761e1f..1052352f 100644
--- a/src/lib/builders/date-field/create.ts
+++ b/src/lib/builders/date-field/create.ts
@@ -274,7 +274,7 @@ export function createDateField(props?: CreateDateFieldProps) {
 				id: $labelId,
 				'data-invalid': $isInvalid ? '' : undefined,
 				'data-disabled': $disabled ? '' : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement) => {
 			const unsub = executeCallbacks(
@@ -307,7 +307,7 @@ export function createDateField(props?: CreateDateFieldProps) {
 				id: $validationId,
 				'data-invalid': $isInvalid ? '' : undefined,
 				style: $isInvalid ? undefined : validStyle,
-			};
+			} as const;
 		},
 	});
 
@@ -346,11 +346,11 @@ export function createDateField(props?: CreateDateFieldProps) {
 				id: $ids.field,
 				'aria-labelledby': $ids.label,
 				'aria-describedby': describedBy,
-				'aria-disabled': $disabled ? ('true' as const) : undefined,
-				'aria-readonly': $readonly ? ('true' as const) : undefined,
+				'aria-disabled': $disabled ? 'true' : undefined,
+				'aria-readonly': $readonly ? 'true' : undefined,
 				'data-invalid': $isInvalid ? '' : undefined,
 				'data-disabled': $disabled ? '' : undefined,
-			};
+			} as const;
 		},
 		// even if we don't need the element we need to specify it
 		// or TS will complain when svelte tries to pass it
@@ -443,9 +443,9 @@ export function createDateField(props?: CreateDateFieldProps) {
 				const inReadonlySegments = $readonlySegmentsSet.has(part);
 				const defaultAttrs = {
 					...getSegmentAttrs(part, props),
-					'aria-invalid': $isInvalid ? ('true' as const) : undefined,
-					'aria-disabled': $disabled ? ('true' as const) : undefined,
-					'aria-readonly': $readonly || inReadonlySegments ? ('true' as const) : undefined,
+					'aria-invalid': $isInvalid ? 'true' : undefined,
+					'aria-disabled': $disabled ? 'true' : undefined,
+					'aria-readonly': $readonly || inReadonlySegments ? 'true' : undefined,
 					'data-invalid': $isInvalid ? '' : undefined,
 					'data-disabled': $disabled ? '' : undefined,
 					'data-segment': `${part}`,
@@ -466,7 +466,7 @@ export function createDateField(props?: CreateDateFieldProps) {
 					contenteditable: $readonly || inReadonlySegments || $disabled ? false : true,
 					'aria-describedby': describedBy,
 					tabindex: $disabled ? undefined : 0,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<DateFieldEvents['segment']> =>
diff --git a/src/lib/builders/date-picker/create.ts b/src/lib/builders/date-picker/create.ts
index 090a273a..2bb4e2aa 100644
--- a/src/lib/builders/date-picker/create.ts
+++ b/src/lib/builders/date-picker/create.ts
@@ -96,7 +96,7 @@ export function createDatePicker(props?: CreateDatePickerProps) {
 				'aria-label': 'Open date picker',
 				'data-segment': 'trigger',
 				disabled: $disabled ? true : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<DatePickerEvents['trigger']> => {
 			const unsubKeydown = addMeltEventListener(node, 'keydown', handleTriggerKeydown);
diff --git a/src/lib/builders/date-range-field/create.ts b/src/lib/builders/date-range-field/create.ts
index 9997b79e..b003c376 100644
--- a/src/lib/builders/date-range-field/create.ts
+++ b/src/lib/builders/date-range-field/create.ts
@@ -182,7 +182,7 @@ export function createDateRangeField(props?: CreateDateRangeFieldProps) {
 				id: $labelId,
 				'data-invalid': $isInvalid ? '' : undefined,
 				'data-disabled': $disabled ? '' : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement) => {
 			const unsub = executeCallbacks(
@@ -229,7 +229,7 @@ export function createDateRangeField(props?: CreateDateRangeFieldProps) {
 				'aria-labelledby': $ids.label,
 				'aria-describedby': describedBy,
 				'data-invalid': $isInvalid ? '' : undefined,
-			};
+			} as const;
 		},
 		action: () => {
 			getAnnouncer();
@@ -252,7 +252,7 @@ export function createDateRangeField(props?: CreateDateRangeFieldProps) {
 				id: $validationId,
 				'data-invalid': $isInvalid ? '' : undefined,
 				style: $isInvalid ? undefined : validStyle,
-			};
+			} as const;
 		},
 	});
 
diff --git a/src/lib/builders/date-range-picker/create.ts b/src/lib/builders/date-range-picker/create.ts
index 5f046587..66664ca4 100644
--- a/src/lib/builders/date-range-picker/create.ts
+++ b/src/lib/builders/date-range-picker/create.ts
@@ -95,7 +95,7 @@ export function createDateRangePicker(props?: CreateDateRangePickerProps) {
 				'aria-label': 'Open date picker',
 				'data-segment': 'trigger',
 				disabled: $disabled ? true : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<DateRangePickerEvents['trigger']> => {
 			const unsubKeydown = addMeltEventListener(node, 'keydown', handleTriggerKeydown);
diff --git a/src/lib/builders/dialog/create.ts b/src/lib/builders/dialog/create.ts
index 0d85213d..24e43af8 100644
--- a/src/lib/builders/dialog/create.ts
+++ b/src/lib/builders/dialog/create.ts
@@ -171,14 +171,14 @@ export function createDialog(props?: CreateDialogProps) {
 				role: role.get(),
 				'aria-describedby': $descriptionId,
 				'aria-labelledby': $titleId,
-				'aria-modal': $isVisible ? ('true' as const) : undefined,
+				'aria-modal': $isVisible ? 'true' : undefined,
 				'data-state': $open ? 'open' : 'closed',
 				tabindex: -1,
 				hidden: $isVisible ? undefined : true,
 				style: styleToString({
 					display: $isVisible ? undefined : 'none',
 				}),
-			};
+			} as const;
 		},
 
 		action: (node: HTMLElement) => {
@@ -186,26 +186,30 @@ export function createDialog(props?: CreateDialogProps) {
 			let deactivate = noop;
 
 			const destroy = executeCallbacks(
-				effect([open, closeOnEscape], ([$open, $closeOnEscape]) => {
-					if (!$open) return;
-
-					const focusTrap = createFocusTrap({
-						immediate: false,
-						escapeDeactivates: $closeOnEscape,
-						clickOutsideDeactivates: true,
-						returnFocusOnDeactivate: false,
-						fallbackFocus: node,
-					});
-
-					activate = focusTrap.activate;
-					deactivate = focusTrap.deactivate;
-					const ac = focusTrap.useFocusTrap(node);
-					if (ac && ac.destroy) {
-						return ac.destroy;
-					} else {
-						return focusTrap.deactivate;
+				effect(
+					[open, closeOnOutsideClick, closeOnEscape],
+					([$open, $closeOnOutsideClick, $closeOnEscape]) => {
+						if (!$open) return;
+
+						const focusTrap = createFocusTrap({
+							immediate: false,
+							escapeDeactivates: $closeOnEscape,
+							clickOutsideDeactivates: $closeOnOutsideClick,
+							allowOutsideClick: true,
+							returnFocusOnDeactivate: false,
+							fallbackFocus: node,
+						});
+
+						activate = focusTrap.activate;
+						deactivate = focusTrap.deactivate;
+						const ac = focusTrap.useFocusTrap(node);
+						if (ac && ac.destroy) {
+							return ac.destroy;
+						} else {
+							return focusTrap.deactivate;
+						}
 					}
-				}),
+				),
 				effect([closeOnOutsideClick, open], ([$closeOnOutsideClick, $open]) => {
 					return useModal(node, {
 						open: $open,
@@ -223,15 +227,7 @@ export function createDialog(props?: CreateDialogProps) {
 				effect([closeOnEscape], ([$closeOnEscape]) => {
 					if (!$closeOnEscape) return noop;
 
-					const escapeKeydown = useEscapeKeydown(node, {
-						handler: () => {
-							handleClose();
-						},
-					});
-					if (escapeKeydown && escapeKeydown.destroy) {
-						return escapeKeydown.destroy;
-					}
-					return noop;
+					return useEscapeKeydown(node, { handler: handleClose }).destroy;
 				}),
 
 				effect([isVisible], ([$isVisible]) => {
@@ -256,20 +252,16 @@ export function createDialog(props?: CreateDialogProps) {
 
 	const portalled = makeElement(name('portalled'), {
 		stores: portal,
-		returned: ($portal) => ({
-			'data-portal': portalAttr($portal),
-		}),
+		returned: ($portal) =>
+			({
+				'data-portal': portalAttr($portal),
+			} as const),
 		action: (node: HTMLElement) => {
 			const unsubPortal = effect([portal], ([$portal]) => {
 				if ($portal === null) return noop;
 				const portalDestination = getPortalDestination(node, $portal);
 				if (portalDestination === null) return noop;
-				const portalAction = usePortal(node, portalDestination);
-				if (portalAction && portalAction.destroy) {
-					return portalAction.destroy;
-				} else {
-					return noop;
-				}
+				return usePortal(node, portalDestination).destroy;
 			});
 
 			return {
@@ -282,16 +274,18 @@ export function createDialog(props?: CreateDialogProps) {
 
 	const title = makeElement(name('title'), {
 		stores: [ids.title],
-		returned: ([$titleId]) => ({
-			id: $titleId,
-		}),
+		returned: ([$titleId]) =>
+			({
+				id: $titleId,
+			} as const),
 	});
 
 	const description = makeElement(name('description'), {
 		stores: [ids.description],
-		returned: ([$descriptionId]) => ({
-			id: $descriptionId,
-		}),
+		returned: ([$descriptionId]) =>
+			({
+				id: $descriptionId,
+			} as const),
 	});
 
 	const close = makeElement(name('close'), {
diff --git a/src/lib/builders/hidden-input/create.ts b/src/lib/builders/hidden-input/create.ts
index 274ba916..2c9d73bc 100644
--- a/src/lib/builders/hidden-input/create.ts
+++ b/src/lib/builders/hidden-input/create.ts
@@ -29,7 +29,7 @@ export function createHiddenInput(props: CreateHiddenInputProps) {
 			return {
 				name: $name,
 				value: $value?.toString(),
-				'aria-hidden': 'true' as const,
+				'aria-hidden': 'true',
 				hidden: true,
 				disabled: $disabled,
 				required: $required,
@@ -41,7 +41,7 @@ export function createHiddenInput(props: CreateHiddenInputProps) {
 					margin: 0,
 					transform: 'translateX(-100%)',
 				}),
-			};
+			} as const;
 		},
 		action: (node: HTMLInputElement) => {
 			// When value changes, emit a change event
diff --git a/src/lib/builders/link-preview/create.ts b/src/lib/builders/link-preview/create.ts
index 0830514e..53c15b18 100644
--- a/src/lib/builders/link-preview/create.ts
+++ b/src/lib/builders/link-preview/create.ts
@@ -116,13 +116,13 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 		stores: [open, ids.trigger, ids.content],
 		returned: ([$open, $triggerId, $contentId]) => {
 			return {
-				role: 'button' as const,
-				'aria-haspopup': 'dialog' as const,
+				role: 'button',
+				'aria-haspopup': 'dialog',
 				'aria-expanded': $open,
 				'data-state': $open ? 'open' : 'closed',
 				'aria-controls': $contentId,
 				id: $triggerId,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<LinkPreviewEvents['trigger']> => {
 			const unsub = executeCallbacks(
@@ -164,7 +164,7 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 				id: $contentId,
 				'data-state': $isVisible ? 'open' : 'closed',
 				'data-portal': portalAttr($portal),
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<LinkPreviewEvents['content']> => {
 			let unsub = noop;
@@ -191,7 +191,8 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 					if (!$isVisible || !$activeTrigger) return;
 
 					tick().then(() => {
-						const popper = usePopper(node, {
+						unsubPopper();
+						unsubPopper = usePopper(node, {
 							anchorElement: $activeTrigger,
 							open: open,
 							options: {
@@ -218,11 +219,7 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 								focusTrap: null,
 								escapeKeydown: $closeOnEscape ? undefined : null,
 							},
-						});
-
-						if (popper && popper.destroy) {
-							unsubPopper = popper.destroy;
-						}
+						}).destroy;
 					});
 				}
 			);
@@ -266,14 +263,15 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 
 	const arrow = makeElement(name('arrow'), {
 		stores: arrowSize,
-		returned: ($arrowSize) => ({
-			'data-arrow': true,
-			style: styleToString({
-				position: 'absolute',
-				width: `var(--arrow-size, ${$arrowSize}px)`,
-				height: `var(--arrow-size, ${$arrowSize}px)`,
-			}),
-		}),
+		returned: ($arrowSize) =>
+			({
+				'data-arrow': true,
+				style: styleToString({
+					position: 'absolute',
+					width: `var(--arrow-size, ${$arrowSize}px)`,
+					height: `var(--arrow-size, ${$arrowSize}px)`,
+				}),
+			} as const),
 	});
 
 	effect([containSelection], ([$containSelection]) => {
diff --git a/src/lib/builders/listbox/create.ts b/src/lib/builders/listbox/create.ts
index 6a14a33b..461f3368 100644
--- a/src/lib/builders/listbox/create.ts
+++ b/src/lib/builders/listbox/create.ts
@@ -38,7 +38,6 @@ import {
 	toggle,
 	withGet,
 } from '$lib/internal/helpers/index.js';
-import { safeOnMount } from '$lib/internal/helpers/lifecycle.js';
 import type { Defaults, MeltActionReturn } from '$lib/internal/types.js';
 import { dequal as deepEqual } from 'dequal';
 import { tick } from 'svelte';
@@ -208,12 +207,6 @@ export function createListbox<
 	async function openMenu() {
 		open.set(true);
 
-		const triggerEl = document.getElementById(ids.trigger.get());
-		if (!triggerEl) return;
-
-		// The active trigger is used to anchor the menu to the input element.
-		if (triggerEl !== activeTrigger.get()) activeTrigger.set(triggerEl);
-
 		// Wait a tick for the menu to open then highlight the selected item.
 		await tick();
 
@@ -290,6 +283,7 @@ export function createListbox<
 			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<ListboxEvents['trigger']> => {
+			activeTrigger.set(node);
 			const isInput = isHTMLInputElement(node);
 
 			const unsubscribe = executeCallbacks(
@@ -366,7 +360,10 @@ export function createListbox<
 						return;
 					}
 					// Pressing enter with a highlighted item should select it.
-					if (e.key === kbd.ENTER || (e.key === kbd.SPACE && isHTMLButtonElement(node))) {
+					if (
+						(e.key === kbd.ENTER && !e.isComposing) ||
+						(e.key === kbd.SPACE && isHTMLButtonElement(node))
+					) {
 						e.preventDefault();
 						const $highlightedItem = highlightedItem.get();
 						if ($highlightedItem) {
@@ -448,6 +445,7 @@ export function createListbox<
 
 			return {
 				destroy() {
+					activeTrigger.set(null);
 					unsubscribe();
 					unsubEscapeKeydown();
 				},
@@ -480,9 +478,10 @@ export function createListbox<
 						if (!$isVisible || !$activeTrigger) return;
 
 						tick().then(() => {
+							unsubPopper();
 							const ignoreHandler = createClickOutsideIgnore(ids.trigger.get());
 
-							const popper = usePopper(node, {
+							unsubPopper = usePopper(node, {
 								anchorElement: $activeTrigger,
 								open,
 								options: {
@@ -509,10 +508,7 @@ export function createListbox<
 									escapeKeydown: null,
 									portal: getPortalDestination(node, $portal),
 								},
-							});
-							if (popper && popper.destroy) {
-								unsubPopper = popper.destroy;
-							}
+							}).destroy;
 						});
 					}
 				)
@@ -538,7 +534,7 @@ export function createListbox<
 			return {
 				id: $labelId,
 				for: $triggerId,
-			};
+			} as const;
 		},
 		action: labelAction,
 	});
@@ -596,18 +592,20 @@ export function createListbox<
 
 	const group = makeElement(name('group'), {
 		returned: () => {
-			return (groupId: string) => ({
-				role: 'group',
-				'aria-labelledby': groupId,
-			});
+			return (groupId: string) =>
+				({
+					role: 'group',
+					'aria-labelledby': groupId,
+				} as const);
 		},
 	});
 
 	const groupLabel = makeElement(name('group-label'), {
 		returned: () => {
-			return (groupId: string) => ({
-				id: groupId,
-			});
+			return (groupId: string) =>
+				({
+					id: groupId,
+				} as const);
 		},
 	});
 
@@ -623,34 +621,21 @@ export function createListbox<
 
 	const arrow = makeElement(name('arrow'), {
 		stores: arrowSize,
-		returned: ($arrowSize) => ({
-			'data-arrow': true,
-			style: styleToString({
-				position: 'absolute',
-				width: `var(--arrow-size, ${$arrowSize}px)`,
-				height: `var(--arrow-size, ${$arrowSize}px)`,
-			}),
-		}),
+		returned: ($arrowSize) =>
+			({
+				'data-arrow': true,
+				style: styleToString({
+					position: 'absolute',
+					width: `var(--arrow-size, ${$arrowSize}px)`,
+					height: `var(--arrow-size, ${$arrowSize}px)`,
+				}),
+			} as const),
 	});
 
 	/* ------------------- */
 	/* LIFECYCLE & EFFECTS */
 	/* ------------------- */
 
-	safeOnMount(() => {
-		if (!isBrowser) return;
-		const menuEl = document.getElementById(ids.menu.get());
-
-		const triggerEl = document.getElementById(ids.trigger.get());
-		if (triggerEl) {
-			activeTrigger.set(triggerEl);
-		}
-
-		if (!menuEl) return;
-		const selectedEl = menuEl.querySelector('[data-selected]');
-		if (!isHTMLElement(selectedEl)) return;
-	});
-
 	/**
 	 * Handles moving the `data-highlighted` attribute between items when
 	 * the user moves their pointer or navigates with their keyboard.
@@ -668,18 +653,9 @@ export function createListbox<
 		});
 	});
 
-	effect([open], ([$open]) => {
-		if (!isBrowser) return;
-
-		let unsubScroll = noop;
-
-		if (preventScroll.get() && $open) {
-			unsubScroll = removeScroll();
-		}
-
-		return () => {
-			unsubScroll();
-		};
+	effect([open, preventScroll], ([$open, $preventScroll]) => {
+		if (!isBrowser || !$open || !$preventScroll) return;
+		return removeScroll();
 	});
 
 	return {
diff --git a/src/lib/builders/listbox/types.ts b/src/lib/builders/listbox/types.ts
index a35c9a01..550b0ef5 100644
--- a/src/lib/builders/listbox/types.ts
+++ b/src/lib/builders/listbox/types.ts
@@ -30,7 +30,7 @@ export type CreateListboxProps<
 
 	/**
 	 * The size of the arrow in pixels.
-	 * @default 8
+	 * @default undefined
 	 */
 	arrowSize?: number;
 
diff --git a/src/lib/builders/menu/create.ts b/src/lib/builders/menu/create.ts
index c275a30a..9c013dbd 100644
--- a/src/lib/builders/menu/create.ts
+++ b/src/lib/builders/menu/create.ts
@@ -185,8 +185,9 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 					unsubPopper();
 					if (!$isVisible || !$rootActiveTrigger) return;
 					tick().then(() => {
+						unsubPopper();
 						setMeltMenuAttribute(node, selector);
-						const popper = usePopper(node, {
+						unsubPopper = usePopper(node, {
 							anchorElement: $rootActiveTrigger,
 							open: rootOpen,
 							options: {
@@ -214,11 +215,7 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 								portal: getPortalDestination(node, $portal),
 								escapeKeydown: $closeOnEscape ? undefined : null,
 							},
-						});
-
-						if (popper && popper.destroy) {
-							unsubPopper = popper.destroy;
-						}
+						}).destroy;
 					});
 				}
 			);
@@ -326,14 +323,15 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 
 	const rootArrow = makeElement(name('arrow'), {
 		stores: arrowSize,
-		returned: ($arrowSize) => ({
-			'data-arrow': true,
-			style: styleToString({
-				position: 'absolute',
-				width: `var(--arrow-size, ${$arrowSize}px)`,
-				height: `var(--arrow-size, ${$arrowSize}px)`,
-			}),
-		}),
+		returned: ($arrowSize) =>
+			({
+				'data-arrow': true,
+				style: styleToString({
+					position: 'absolute',
+					width: `var(--arrow-size, ${$arrowSize}px)`,
+					height: `var(--arrow-size, ${$arrowSize}px)`,
+				}),
+			} as const),
 	});
 
 	const overlay = makeElement(name('overlay'), {
@@ -369,12 +367,7 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 				if ($portal === null) return noop;
 				const portalDestination = getPortalDestination(node, $portal);
 				if (portalDestination === null) return noop;
-				const portalAction = usePortal(node, portalDestination);
-				if (portalAction && portalAction.destroy) {
-					return portalAction.destroy;
-				} else {
-					return noop;
-				}
+				return usePortal(node, portalDestination).destroy;
 			});
 
 			return {
@@ -392,7 +385,7 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 				role: 'menuitem',
 				tabindex: -1,
 				'data-orientation': 'vertical',
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<MenuEvents['item']> => {
 			setMeltMenuAttribute(node, selector);
@@ -452,18 +445,20 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 
 	const group = makeElement(name('group'), {
 		returned: () => {
-			return (groupId: string) => ({
-				role: 'group',
-				'aria-labelledby': groupId,
-			});
+			return (groupId: string) =>
+				({
+					role: 'group',
+					'aria-labelledby': groupId,
+				} as const);
 		},
 	});
 
 	const groupLabel = makeElement(name('group-label'), {
 		returned: () => {
-			return (groupId: string) => ({
-				id: groupId,
-			});
+			return (groupId: string) =>
+				({
+					id: groupId,
+				} as const);
 		},
 	});
 
@@ -586,9 +581,10 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 		const value = overridable(valueWritable, args.onValueChange);
 
 		const radioGroup = makeElement(name('radio-group'), {
-			returned: () => ({
-				role: 'group',
-			}),
+			returned: () =>
+				({
+					role: 'group',
+				} as const),
 		});
 
 		const radioItemDefaults = {
@@ -611,7 +607,7 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 						'data-value': itemValue,
 						'data-orientation': 'vertical',
 						tabindex: -1,
-					};
+					} as const;
 				};
 			},
 			action: (node: HTMLElement): MeltActionReturn<MenuEvents['radioItem']> => {
@@ -792,9 +788,10 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 						const activeTrigger = subActiveTrigger.get();
 						if (!activeTrigger) return;
 						tick().then(() => {
+							unsubPopper();
 							const parentMenuEl = getParentMenu(activeTrigger);
 
-							const popper = usePopper(node, {
+							unsubPopper = usePopper(node, {
 								anchorElement: activeTrigger,
 								open: subOpen,
 								options: {
@@ -804,11 +801,7 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 									focusTrap: null,
 									escapeKeydown: null,
 								},
-							});
-
-							if (popper && popper.destroy) {
-								unsubPopper = popper.destroy;
-							}
+							}).destroy;
 						});
 					}
 				);
@@ -1075,14 +1068,15 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 
 		const subArrow = makeElement(name('subarrow'), {
 			stores: arrowSize,
-			returned: ($arrowSize) => ({
-				'data-arrow': true,
-				style: styleToString({
-					position: 'absolute',
-					width: `var(--arrow-size, ${$arrowSize}px)`,
-					height: `var(--arrow-size, ${$arrowSize}px)`,
-				}),
-			}),
+			returned: ($arrowSize) =>
+				({
+					'data-arrow': true,
+					style: styleToString({
+						position: 'absolute',
+						width: `var(--arrow-size, ${$arrowSize}px)`,
+						height: `var(--arrow-size, ${$arrowSize}px)`,
+					}),
+				} as const),
 		});
 
 		/* -------------------------------------------------------------------------------------------------
diff --git a/src/lib/builders/menubar/create.ts b/src/lib/builders/menubar/create.ts
index 0eaff1f0..9777b3cb 100644
--- a/src/lib/builders/menubar/create.ts
+++ b/src/lib/builders/menubar/create.ts
@@ -163,7 +163,8 @@ export function createMenubar(props?: CreateMenubarProps) {
 						if (!($rootOpen && $rootActiveTrigger)) return;
 
 						tick().then(() => {
-							const popper = usePopper(node, {
+							unsubPopper();
+							unsubPopper = usePopper(node, {
 								anchorElement: $rootActiveTrigger,
 								open: rootOpen,
 								options: {
@@ -186,11 +187,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 										open: $rootOpen,
 									},
 								},
-							});
-
-							if (popper && popper.destroy) {
-								unsubPopper = popper.destroy;
-							}
+							}).destroy;
 						});
 					}
 				);
diff --git a/src/lib/builders/pagination/create.ts b/src/lib/builders/pagination/create.ts
index 0fc758b9..f5952823 100644
--- a/src/lib/builders/pagination/create.ts
+++ b/src/lib/builders/pagination/create.ts
@@ -46,9 +46,10 @@ export function createPagination(props: CreatePaginationProps) {
 	});
 
 	const root = makeElement(name(), {
-		returned: () => ({
-			'data-scope': 'pagination',
-		}),
+		returned: () =>
+			({
+				'data-scope': 'pagination',
+			} as const),
 	});
 
 	const pages = derived([page, totalPages, siblingCount], ([$page, $totalPages, $siblingCount]) => {
@@ -99,7 +100,7 @@ export function createPagination(props: CreatePaginationProps) {
 					'aria-label': `Page ${pageItem.value}`,
 					'data-value': pageItem.value,
 					'data-selected': pageItem.value === $page ? '' : undefined,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<PaginationEvents['pageTrigger']> => {
diff --git a/src/lib/builders/pin-input/create.ts b/src/lib/builders/pin-input/create.ts
index ee780cff..4e8f774d 100644
--- a/src/lib/builders/pin-input/create.ts
+++ b/src/lib/builders/pin-input/create.ts
@@ -68,7 +68,7 @@ export function createPinInput(props?: CreatePinInputProps) {
 			return {
 				id: $rootId,
 				'data-complete': $value.length && $value.every((v) => v.length > 0) ? '' : undefined,
-			};
+			} as const;
 		},
 	});
 
@@ -98,7 +98,7 @@ export function createPinInput(props?: CreatePinInputProps) {
 					disabled: disabledAttr($disabled),
 					type: $type,
 					value: currValue,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLInputElement): MeltActionReturn<PinInputEvents['input']> => {
diff --git a/src/lib/builders/popover/create.ts b/src/lib/builders/popover/create.ts
index f3c145e3..ddc8ef49 100644
--- a/src/lib/builders/popover/create.ts
+++ b/src/lib/builders/popover/create.ts
@@ -108,7 +108,7 @@ export function createPopover(args?: CreatePopoverProps) {
 				id: $contentId,
 				'data-state': $isVisible ? 'open' : 'closed',
 				'data-portal': portalAttr($portal),
-			};
+			} as const;
 		},
 		action: (node: HTMLElement) => {
 			let unsubPopper = noop;
@@ -136,7 +136,8 @@ export function createPopover(args?: CreatePopoverProps) {
 					if (!$isVisible || !$activeTrigger) return;
 
 					tick().then(() => {
-						const popper = usePopper(node, {
+						unsubPopper();
+						unsubPopper = usePopper(node, {
 							anchorElement: $activeTrigger,
 							open,
 							options: {
@@ -145,7 +146,8 @@ export function createPopover(args?: CreatePopoverProps) {
 									? null
 									: {
 											returnFocusOnDeactivate: false,
-											clickOutsideDeactivates: true,
+											clickOutsideDeactivates: $closeOnOutsideClick,
+											allowOutsideClick: true,
 											escapeDeactivates: $closeOnEscape,
 									  },
 								modal: {
@@ -163,11 +165,7 @@ export function createPopover(args?: CreatePopoverProps) {
 									: null,
 								portal: getPortalDestination(node, $portal),
 							},
-						});
-
-						if (popper && popper.destroy) {
-							unsubPopper = popper.destroy;
-						}
+						}).destroy;
 					});
 				}
 			);
@@ -246,6 +244,8 @@ export function createPopover(args?: CreatePopoverProps) {
 		},
 		action: (node: HTMLElement) => {
 			let unsubEscapeKeydown = noop;
+			let unsubDerived = noop;
+			let unsubPortal = noop;
 
 			if (closeOnEscape.get()) {
 				const escapeKeydown = useEscapeKeydown(node, {
@@ -258,21 +258,18 @@ export function createPopover(args?: CreatePopoverProps) {
 				}
 			}
 
-			const unsubPortal = effect([portal], ([$portal]) => {
-				if ($portal === null) return noop;
+			unsubDerived = effect([portal], ([$portal]) => {
+				unsubPortal();
+				if ($portal === null) return;
 				const portalDestination = getPortalDestination(node, $portal);
-				if (portalDestination === null) return noop;
-				const portalAction = usePortal(node, portalDestination);
-				if (portalAction && portalAction.destroy) {
-					return portalAction.destroy;
-				} else {
-					return noop;
-				}
+				if (portalDestination === null) return;
+				unsubPortal = usePortal(node, portalDestination).destroy;
 			});
 
 			return {
 				destroy() {
 					unsubEscapeKeydown();
+					unsubDerived();
 					unsubPortal();
 				},
 			};
@@ -281,14 +278,15 @@ export function createPopover(args?: CreatePopoverProps) {
 
 	const arrow = makeElement(name('arrow'), {
 		stores: arrowSize,
-		returned: ($arrowSize) => ({
-			'data-arrow': true,
-			style: styleToString({
-				position: 'absolute',
-				width: `var(--arrow-size, ${$arrowSize}px)`,
-				height: `var(--arrow-size, ${$arrowSize}px)`,
-			}),
-		}),
+		returned: ($arrowSize) =>
+			({
+				'data-arrow': true,
+				style: styleToString({
+					position: 'absolute',
+					width: `var(--arrow-size, ${$arrowSize}px)`,
+					height: `var(--arrow-size, ${$arrowSize}px)`,
+				}),
+			} as const),
 	});
 
 	const close = makeElement(name('close'), {
diff --git a/src/lib/builders/progress/create.ts b/src/lib/builders/progress/create.ts
index 5c3f725f..3d61d918 100644
--- a/src/lib/builders/progress/create.ts
+++ b/src/lib/builders/progress/create.ts
@@ -37,7 +37,7 @@ export const createProgress = (props?: CreateProgressProps) => {
 				'data-value': $value,
 				'data-state': $value === null ? 'indeterminate' : $value === $max ? 'complete' : 'loading',
 				'data-max': $max,
-			};
+			} as const;
 		},
 	});
 
diff --git a/src/lib/builders/range-calendar/create.ts b/src/lib/builders/range-calendar/create.ts
index a4ff495c..1353d0b5 100644
--- a/src/lib/builders/range-calendar/create.ts
+++ b/src/lib/builders/range-calendar/create.ts
@@ -285,7 +285,7 @@ export function createRangeCalendar<T extends DateValue = DateValue>(
 				'data-invalid': $isInvalid ? '' : undefined,
 				'data-disabled': $disabled ? '' : undefined,
 				'data-readonly': $readonly ? '' : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<RangeCalendarEvents['calendar']> => {
 			/**
@@ -312,20 +312,21 @@ export function createRangeCalendar<T extends DateValue = DateValue>(
 			return {
 				'aria-hidden': true,
 				'data-disabled': $disabled ? '' : undefined,
-			};
+			} as const;
 		},
 	});
 
 	const grid = makeElement(name('grid'), {
 		stores: [readonly, disabled],
-		returned: ([$readonly, $disabled]) => ({
-			tabindex: -1,
-			role: 'grid',
-			'aria-readonly': $readonly ? ('true' as const) : undefined,
-			'aria-disabled': $disabled ? ('true' as const) : undefined,
-			'data-readonly': $readonly ? '' : undefined,
-			'data-disabled': $disabled ? '' : undefined,
-		}),
+		returned: ([$readonly, $disabled]) =>
+			({
+				tabindex: -1,
+				role: 'grid',
+				'aria-readonly': $readonly ? 'true' : undefined,
+				'aria-disabled': $disabled ? 'true' : undefined,
+				'data-readonly': $readonly ? '' : undefined,
+				'data-disabled': $disabled ? '' : undefined,
+			} as const),
 	});
 
 	const prevButton = makeElement(name('prevButton'), {
@@ -336,10 +337,10 @@ export function createRangeCalendar<T extends DateValue = DateValue>(
 				role: 'button',
 				type: 'button' as const,
 				'aria-label': 'Previous',
-				'aria-disabled': disabled ? ('true' as const) : undefined,
+				'aria-disabled': disabled ? 'true' : undefined,
 				disabled: disabled ? true : undefined,
 				'data-disabled': disabled ? '' : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<RangeCalendarEvents['prevButton']> => {
 			const unsub = executeCallbacks(
@@ -361,10 +362,10 @@ export function createRangeCalendar<T extends DateValue = DateValue>(
 				role: 'button',
 				type: 'button' as const,
 				'aria-label': 'Next',
-				'aria-disabled': disabled ? ('true' as const) : undefined,
+				'aria-disabled': disabled ? 'true' : undefined,
 				disabled: disabled ? true : undefined,
 				'data-disabled': disabled ? '' : undefined,
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<RangeCalendarEvents['nextButton']> => {
 			const unsub = executeCallbacks(
diff --git a/src/lib/builders/separator/create.ts b/src/lib/builders/separator/create.ts
index d4d2aeab..c2133db1 100644
--- a/src/lib/builders/separator/create.ts
+++ b/src/lib/builders/separator/create.ts
@@ -21,7 +21,7 @@ export const createSeparator = (props?: CreateSeparatorProps) => {
 				'aria-orientation': ariaOrientation,
 				'aria-hidden': $decorative,
 				'data-orientation': $orientation,
-			};
+			} as const;
 		},
 	});
 
diff --git a/src/lib/builders/slider/create.ts b/src/lib/builders/slider/create.ts
index b2fa5e22..fa647551 100644
--- a/src/lib/builders/slider/create.ts
+++ b/src/lib/builders/slider/create.ts
@@ -125,7 +125,7 @@ export const createSlider = (props?: CreateSliderProps) => {
 					: `touch-action: ${$orientation === 'horizontal' ? 'pan-y' : 'pan-x'}`,
 
 				'data-melt-id': meltIds.root,
-			};
+			} as const;
 		},
 	});
 
@@ -164,7 +164,7 @@ export const createSlider = (props?: CreateSliderProps) => {
 
 			return {
 				style: styleToString(style),
-			};
+			} as const;
 		},
 	});
 
@@ -394,7 +394,7 @@ export const createSlider = (props?: CreateSliderProps) => {
 					'data-bounded': bounded ? true : undefined,
 					'data-value': tickValue,
 					style: styleToString(style),
-				};
+				} as const;
 			});
 		},
 	});
diff --git a/src/lib/builders/switch/create.ts b/src/lib/builders/switch/create.ts
index 4d1aa06e..74724849 100644
--- a/src/lib/builders/switch/create.ts
+++ b/src/lib/builders/switch/create.ts
@@ -74,7 +74,7 @@ export function createSwitch(props?: CreateSwitchProps) {
 		stores: [checked, nameStore, required, disabled, value],
 		returned: ([$checked, $name, $required, $disabled, $value]) => {
 			return {
-				type: 'checkbox' as const,
+				type: 'checkbox',
 				'aria-hidden': true,
 				hidden: true,
 				tabindex: -1,
diff --git a/src/lib/builders/table-of-contents/create.ts b/src/lib/builders/table-of-contents/create.ts
index 043d3d13..978d7345 100644
--- a/src/lib/builders/table-of-contents/create.ts
+++ b/src/lib/builders/table-of-contents/create.ts
@@ -355,7 +355,7 @@ export function createTableOfContents(args: CreateTableOfContentsArgs) {
 				return {
 					'data-id': id,
 					'data-active': active ? '' : undefined,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLAnchorElement) => {
diff --git a/src/lib/builders/tabs/create.ts b/src/lib/builders/tabs/create.ts
index 8dac2383..96cdb5d7 100644
--- a/src/lib/builders/tabs/create.ts
+++ b/src/lib/builders/tabs/create.ts
@@ -50,7 +50,7 @@ export function createTabs(props?: CreateTabsProps) {
 		returned: ($orientation) => {
 			return {
 				'data-orientation': $orientation,
-			};
+			} as const;
 		},
 	});
 
@@ -62,7 +62,7 @@ export function createTabs(props?: CreateTabsProps) {
 				role: 'tablist',
 				'aria-orientation': $orientation,
 				'data-orientation': $orientation,
-			};
+			} as const;
 		},
 	});
 
@@ -90,7 +90,7 @@ export function createTabs(props?: CreateTabsProps) {
 				const isActive = sourceOfTruth === tabValue;
 
 				return {
-					type: 'button' as const,
+					type: 'button',
 					role: 'tab',
 					'data-state': isActive ? 'active' : 'inactive',
 					tabindex: isActive ? 0 : -1,
@@ -98,7 +98,7 @@ export function createTabs(props?: CreateTabsProps) {
 					'data-orientation': $orientation,
 					'data-disabled': disabledAttr(disabled),
 					disabled: disabledAttr(disabled),
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<TabsEvents['trigger']> => {
@@ -194,7 +194,7 @@ export function createTabs(props?: CreateTabsProps) {
 						? undefined
 						: true,
 					tabindex: 0,
-				};
+				} as const;
 			};
 		},
 	});
diff --git a/src/lib/builders/tags-input/create.ts b/src/lib/builders/tags-input/create.ts
index 12c1c6b3..5e5127f0 100644
--- a/src/lib/builders/tags-input/create.ts
+++ b/src/lib/builders/tags-input/create.ts
@@ -262,7 +262,7 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 				'data-disabled': disabledAttr($disabled),
 				disabled: disabledAttr($disabled),
 				placeholder: $placeholder,
-			};
+			} as const;
 		},
 		action: (node: HTMLInputElement): MeltActionReturn<TagsInputEvents['input']> => {
 			const getTagsInfo = (id: string) => {
@@ -497,7 +497,7 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 								margin: 0,
 						  })
 						: undefined,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLDivElement): MeltActionReturn<TagsInputEvents['tag']> => {
@@ -583,7 +583,7 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 					'data-disabled': disabledAttr(disabled),
 					disabled: disabledAttr(disabled),
 					tabindex: -1,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<TagsInputEvents['deleteTrigger']> => {
@@ -638,7 +638,7 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 								margin: 0,
 						  })
 						: undefined,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<TagsInputEvents['edit']> => {
diff --git a/src/lib/builders/toast/create.ts b/src/lib/builders/toast/create.ts
index fd7d104a..2576e73a 100644
--- a/src/lib/builders/toast/create.ts
+++ b/src/lib/builders/toast/create.ts
@@ -111,9 +111,9 @@ export function createToaster<T = object>(props?: CreateToasterProps) {
 					role: 'alert',
 					'aria-describedby': toast.ids.description,
 					'aria-labelledby': toast.ids.title,
-					'aria-live': toast.type === 'foreground' ? ('assertive' as const) : ('polite' as const),
+					'aria-live': toast.type === 'foreground' ? 'assertive' : 'polite',
 					tabindex: -1,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<ToastEvents['content']> => {
@@ -170,7 +170,7 @@ export function createToaster<T = object>(props?: CreateToasterProps) {
 				if (!toast) return null;
 				return {
 					id: toast.ids.title,
-				};
+				} as const;
 			};
 		},
 	});
@@ -184,17 +184,18 @@ export function createToaster<T = object>(props?: CreateToasterProps) {
 
 				return {
 					id: toast.ids.description,
-				};
+				} as const;
 			};
 		},
 	});
 
 	const close = makeElement(name('close'), {
 		returned: () => {
-			return (id: string) => ({
-				type: 'button' as const,
-				'data-id': id,
-			});
+			return (id: string) =>
+				({
+					type: 'button',
+					'data-id': id,
+				} as const);
 		},
 		action: (node: HTMLElement): MeltActionReturn<ToastEvents['close']> => {
 			function handleClose() {
diff --git a/src/lib/builders/toolbar/create.ts b/src/lib/builders/toolbar/create.ts
index 051ab1c4..e810e9eb 100644
--- a/src/lib/builders/toolbar/create.ts
+++ b/src/lib/builders/toolbar/create.ts
@@ -39,7 +39,7 @@ export const createToolbar = (props?: CreateToolbarProps) => {
 			return {
 				role: 'toolbar',
 				'data-orientation': $orientation,
-			};
+			} as const;
 		},
 	});
 
diff --git a/src/lib/builders/tooltip/create.ts b/src/lib/builders/tooltip/create.ts
index 9d9ffe89..48e101ee 100644
--- a/src/lib/builders/tooltip/create.ts
+++ b/src/lib/builders/tooltip/create.ts
@@ -142,7 +142,7 @@ export function createTooltip(props?: CreateTooltipProps) {
 				'aria-describedby': $contentId,
 				id: $triggerId,
 				'data-state': $open ? 'open' : 'closed',
-			};
+			} as const;
 		},
 		action: (node: HTMLElement): MeltActionReturn<TooltipEvents['trigger']> => {
 			const keydownHandler = (e: KeyboardEvent) => {
@@ -204,7 +204,7 @@ export function createTooltip(props?: CreateTooltipProps) {
 				id: $contentId,
 				'data-portal': portalAttr($portal),
 				'data-state': $open ? 'open' : 'closed',
-			});
+			} as const);
 		},
 		action: (node: HTMLElement): MeltActionReturn<TooltipEvents['content']> => {
 			let unsubFloating = noop;
@@ -213,28 +213,17 @@ export function createTooltip(props?: CreateTooltipProps) {
 			const unsubDerived = effect(
 				[isVisible, positioning, portal],
 				([$isVisible, $positioning, $portal]) => {
+					unsubPortal();
+					unsubFloating();
 					const triggerEl = getEl('trigger');
-					if (!$isVisible || !triggerEl) {
-						unsubPortal();
-						unsubFloating();
-						return;
-					}
+					if (!$isVisible || !triggerEl) return;
 
 					tick().then(() => {
-						if ($portal === null) {
-							unsubPortal();
-						} else {
-							const portalDest = getPortalDestination(node, $portal);
-							if (portalDest) {
-								const portalReturn = usePortal(node, portalDest);
-								if (portalReturn && portalReturn.destroy) {
-									unsubPortal = portalReturn.destroy;
-								}
-							}
-						}
-
-						const floatingReturn = useFloating(triggerEl, node, $positioning);
-						unsubFloating = floatingReturn.destroy;
+						unsubPortal();
+						unsubFloating();
+						const portalDest = getPortalDestination(node, $portal);
+						if (portalDest) unsubPortal = usePortal(node, portalDest).destroy;
+						unsubFloating = useFloating(triggerEl, node, $positioning).destroy;
 					});
 				}
 			);
@@ -272,14 +261,15 @@ export function createTooltip(props?: CreateTooltipProps) {
 
 	const arrow = makeElement(name('arrow'), {
 		stores: arrowSize,
-		returned: ($arrowSize) => ({
-			'data-arrow': true,
-			style: styleToString({
-				position: 'absolute',
-				width: `var(--arrow-size, ${$arrowSize}px)`,
-				height: `var(--arrow-size, ${$arrowSize}px)`,
-			}),
-		}),
+		returned: ($arrowSize) =>
+			({
+				'data-arrow': true,
+				style: styleToString({
+					position: 'absolute',
+					width: `var(--arrow-size, ${$arrowSize}px)`,
+					height: `var(--arrow-size, ${$arrowSize}px)`,
+				}),
+			} as const),
 	});
 
 	let isMouseInTooltipArea = false;
diff --git a/src/lib/builders/tree/create.ts b/src/lib/builders/tree/create.ts
index d7bacbb2..2194ffe0 100644
--- a/src/lib/builders/tree/create.ts
+++ b/src/lib/builders/tree/create.ts
@@ -96,7 +96,7 @@ export function createTreeView(args?: CreateTreeViewProps) {
 					'data-id': id,
 					tabindex,
 					'aria-expanded': hasChildren ? $expanded.includes(id) : undefined,
-				};
+				} as const;
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<TreeEvents['item']> => {
@@ -252,14 +252,15 @@ export function createTreeView(args?: CreateTreeViewProps) {
 	const group = makeElement(name('group'), {
 		stores: [expanded],
 		returned: ([$expanded]) => {
-			return (opts: { id: string }) => ({
-				role: 'group',
-				'data-group-id': opts.id,
-				hidden: !forceVisible && !$expanded.includes(opts.id) ? true : undefined,
-				style: styleToString({
-					display: !forceVisible && !$expanded.includes(opts.id) ? 'none' : undefined,
-				}),
-			});
+			return (opts: { id: string }) =>
+				({
+					role: 'group',
+					'data-group-id': opts.id,
+					hidden: !forceVisible && !$expanded.includes(opts.id) ? true : undefined,
+					style: styleToString({
+						display: !forceVisible && !$expanded.includes(opts.id) ? 'none' : undefined,
+					}),
+				} as const);
 		},
 	});
 
diff --git a/src/lib/internal/actions/escape-keydown/action.ts b/src/lib/internal/actions/escape-keydown/action.ts
index 4181b555..04c6190c 100644
--- a/src/lib/internal/actions/escape-keydown/action.ts
+++ b/src/lib/internal/actions/escape-keydown/action.ts
@@ -3,6 +3,7 @@ import { isFunction, isHTMLElement, isReadable } from '$lib/internal/helpers/is.
 import { get, readable, type Readable } from 'svelte/store';
 import { effect, executeCallbacks, kbd, noop } from '../../helpers/index.js';
 import type { EscapeKeydownConfig } from './types.js';
+import type { Action } from 'svelte/action';
 
 /**
  * Creates a readable store that tracks the latest Escape Keydown that occurred on the document.
@@ -35,7 +36,7 @@ const documentEscapeKeyStore = readable<KeyboardEvent | undefined>(
 	}
 );
 
-export const useEscapeKeydown = (node: HTMLElement, config: EscapeKeydownConfig = {}) => {
+export const useEscapeKeydown = ((node, config = {}) => {
 	let unsub = noop;
 	function update(config: EscapeKeydownConfig = {}) {
 		unsub();
@@ -96,4 +97,4 @@ export const useEscapeKeydown = (node: HTMLElement, config: EscapeKeydownConfig
 			unsub();
 		},
 	};
-};
+}) satisfies Action<HTMLElement, EscapeKeydownConfig>;
diff --git a/src/lib/internal/actions/focus-trap/action.ts b/src/lib/internal/actions/focus-trap/action.ts
index 4003c4f8..8cd1e7f2 100644
--- a/src/lib/internal/actions/focus-trap/action.ts
+++ b/src/lib/internal/actions/focus-trap/action.ts
@@ -7,7 +7,7 @@ import type { FocusTrapConfig, FocusTrapReturn } from './types.js';
 import { writable, readonly } from 'svelte/store';
 import { createFocusTrap as _createFocusTrap } from 'focus-trap';
 
-export function createFocusTrap(config: FocusTrapConfig = {}): FocusTrapReturn {
+export function createFocusTrap(config: FocusTrapConfig = {}) {
 	let trap: undefined | FocusTrap;
 
 	const { immediate, ...focusTrapOptions } = config;
@@ -66,5 +66,5 @@ export function createFocusTrap(config: FocusTrapConfig = {}): FocusTrapReturn {
 		deactivate,
 		pause,
 		unpause,
-	};
+	} satisfies FocusTrapReturn;
 }
diff --git a/src/lib/internal/actions/interact-outside/action.ts b/src/lib/internal/actions/interact-outside/action.ts
index 4fcdbb2d..28638d38 100644
--- a/src/lib/internal/actions/interact-outside/action.ts
+++ b/src/lib/internal/actions/interact-outside/action.ts
@@ -5,9 +5,11 @@ import {
 	noop,
 } from '$lib/internal/helpers/index.js';
 import type { InteractOutsideConfig, InteractOutsideEvent } from './types.js';
+import type { Action } from 'svelte/action';
 
-export function useInteractOutside(node: HTMLElement, config: InteractOutsideConfig) {
+export const useInteractOutside = ((node, config) => {
 	let unsub = noop;
+	let unsubClick = noop;
 
 	let isPointerDown = false;
 	let isPointerDownInside = false;
@@ -15,6 +17,7 @@ export function useInteractOutside(node: HTMLElement, config: InteractOutsideCon
 
 	function update(config: InteractOutsideConfig) {
 		unsub();
+		unsubClick();
 		const { onInteractOutside, onInteractOutsideStart, enabled } = config;
 
 		if (!enabled) return;
@@ -41,10 +44,29 @@ export function useInteractOutside(node: HTMLElement, config: InteractOutsideCon
 		// Use pointer events if available, otherwise use mouse/touch events
 		if (typeof PointerEvent !== 'undefined') {
 			const onPointerUp = (e: PointerEvent) => {
-				if (shouldTriggerInteractOutside(e)) {
-					triggerInteractOutside(e);
+				unsubClick();
+
+				const handler = (e: InteractOutsideEvent) => {
+					if (shouldTriggerInteractOutside(e)) {
+						triggerInteractOutside(e);
+					}
+					resetPointerState();
+				};
+
+				/**
+				 * On touch devices, we need to wait for a click event because browsers implement
+				 * a delay between the time the user stops touching the display and when the
+				 * browser executes the click event. Without waiting for the click event, the
+				 * browser may execute events on other elements that should have been prevented.
+				 */
+				if (e.pointerType === 'touch') {
+					unsubClick = addEventListener(documentObj, 'click', handler, {
+						capture: true,
+						once: true,
+					});
+					return;
 				}
-				resetPointerState();
+				handler(e);
 			};
 
 			unsub = executeCallbacks(
@@ -94,9 +116,12 @@ export function useInteractOutside(node: HTMLElement, config: InteractOutsideCon
 
 	return {
 		update,
-		destroy: unsub,
+		destroy() {
+			unsub();
+			unsubClick();
+		},
 	};
-}
+}) satisfies Action<HTMLElement, InteractOutsideConfig>;
 
 function isValidEvent(e: InteractOutsideEvent, node: HTMLElement): boolean {
 	if ('button' in e && e.button > 0) return false;
diff --git a/src/lib/internal/actions/modal/action.ts b/src/lib/internal/actions/modal/action.ts
index 5c4fb7b4..8cd452f3 100644
--- a/src/lib/internal/actions/modal/action.ts
+++ b/src/lib/internal/actions/modal/action.ts
@@ -2,10 +2,10 @@ import { isElement, last, noop, sleep } from '$lib/internal/helpers/index.js';
 import type { InteractOutsideEvent } from '../interact-outside/types.js';
 import { useInteractOutside } from '../index.js';
 import type { ModalConfig } from './types.js';
+import type { Action } from 'svelte/action';
 
 const visibleModals: Element[] = [];
-
-export function useModal(node: HTMLElement, config: ModalConfig) {
+export const useModal = ((node, config) => {
 	let unsubInteractOutside = noop;
 
 	function removeNodeFromVisibleModals() {
@@ -69,4 +69,4 @@ export function useModal(node: HTMLElement, config: ModalConfig) {
 			unsubInteractOutside();
 		},
 	};
-}
+}) satisfies Action<HTMLElement, ModalConfig>;
diff --git a/src/lib/internal/actions/popper/action.ts b/src/lib/internal/actions/popper/action.ts
index f17366a3..1b692c76 100644
--- a/src/lib/internal/actions/popper/action.ts
+++ b/src/lib/internal/actions/popper/action.ts
@@ -22,7 +22,7 @@ const defaultConfig = {
 	portal: 'body',
 } satisfies PopperConfig;
 
-export const usePopper: Action<HTMLElement, PopperArgs> = (popperElement, args) => {
+export const usePopper = ((popperElement, args) => {
 	popperElement.dataset.escapee = '';
 	const { anchorElement, open, options } = args as PopperArgs;
 	if (!anchorElement || !open || !options) {
@@ -34,10 +34,7 @@ export const usePopper: Action<HTMLElement, PopperArgs> = (popperElement, args)
 	const callbacks: Callback[] = [];
 
 	if (opts.portal !== null) {
-		const portal = usePortal(popperElement, opts.portal);
-		if (portal?.destroy) {
-			callbacks.push(portal.destroy);
-		}
+		callbacks.push(usePortal(popperElement, opts.portal).destroy);
 	}
 
 	callbacks.push(useFloating(anchorElement, popperElement, opts.floating).destroy);
@@ -53,11 +50,7 @@ export const usePopper: Action<HTMLElement, PopperArgs> = (popperElement, args)
 			...opts.focusTrap,
 		});
 
-		const usedFocusTrap = useFocusTrap(popperElement);
-
-		if (usedFocusTrap?.destroy) {
-			callbacks.push(usedFocusTrap.destroy);
-		}
+		callbacks.push(useFocusTrap(popperElement).destroy);
 	}
 
 	if (opts.modal !== null) {
@@ -103,4 +96,4 @@ export const usePopper: Action<HTMLElement, PopperArgs> = (popperElement, args)
 			unsubscribe();
 		},
 	};
-};
+}) satisfies Action<HTMLElement, PopperArgs>;
diff --git a/src/lib/internal/actions/portal.ts b/src/lib/internal/actions/portal.ts
index 50e0e3c6..f2581099 100644
--- a/src/lib/internal/actions/portal.ts
+++ b/src/lib/internal/actions/portal.ts
@@ -4,7 +4,7 @@ import { isHTMLElement, noop } from '$lib/internal/helpers/index.js';
 
 export type PortalConfig = string | HTMLElement | undefined;
 
-export const usePortal: Action<HTMLElement, PortalConfig> = (el, target = 'body') => {
+export const usePortal = ((el, target = 'body') => {
 	let targetEl;
 
 	if (!isHTMLElement(target) && typeof target !== 'string') {
@@ -47,4 +47,4 @@ export const usePortal: Action<HTMLElement, PortalConfig> = (el, target = 'body'
 		update,
 		destroy,
 	};
-};
+}) satisfies Action<HTMLElement, PortalConfig>;


Test patch:
diff --git a/e2e/dialog.spec.ts b/e2e/dialog.spec.ts
new file mode 100644
index 00000000..e3f94dfc
--- /dev/null
+++ b/e2e/dialog.spec.ts
@@ -0,0 +1,74 @@
+import { test, expect, type Page, Locator } from '@playwright/test';
+import { jsAvailable } from './utils.js';
+
+const locators = {
+	trigger: '[data-melt-dialog-trigger]',
+	overlay: '[data-melt-dialog-overlay][data-state="open"]',
+	content: '[data-melt-dialog-content][data-state="open"]',
+};
+
+async function nav(page: Page) {
+	await page.goto('/docs/builders/dialog');
+	await jsAvailable(page);
+}
+
+test.describe.configure({ mode: 'parallel' });
+
+test.describe('dialog', () => {
+	test.beforeEach(async ({ page }) => {
+		await nav(page);
+	});
+
+	test.describe('touch device', () => {
+		test("doesn't leak events when tapping overlay", async ({ page }) => {
+			const trigger = page.locator(locators.trigger).first();
+			const content = page.locator(locators.content).first();
+			const viewCodeButton = page.getByLabel('View code').first();
+
+			await trigger.click();
+			await expect(content).toBeVisible();
+
+			const [centerX, centerY] = await getElementCenterPosition(viewCodeButton);
+
+			// Make sure "View Code" Button is not under the dialog, but under the overlay.
+			const isButtonUnderneathContent = await isPositionInsideElement(content, centerX, centerY);
+			expect(isButtonUnderneathContent).toBe(false);
+
+			// Tap the overlay at the position of the "View code" button
+			await page.tap(locators.overlay, { position: { x: centerX, y: centerY } });
+
+			await expect(content).not.toBeVisible();
+
+			// Verify the state of the "View Code" is "unchecked".
+			const viewCodeState = await viewCodeButton.getAttribute('data-state');
+			expect(viewCodeState).toBe('unchecked');
+		});
+	});
+});
+
+/**
+ * Retrieves the bounding box of an element and throws an error if `null`.
+ */
+const getBoundingBox = async (locator: Locator) => {
+	const bounding = await locator.boundingBox();
+	if (!bounding) throw new Error(`Failed to get position of '${locator}'`);
+	return bounding;
+};
+
+/**
+ * Retrieves the center position (x, y coordinates) of an element represented by a Locator.
+ */
+const getElementCenterPosition = async (locator: Locator) => {
+	const bounding = await getBoundingBox(locator);
+	return [bounding.x + bounding.width / 2, bounding.y + bounding.height / 2] as const;
+};
+
+/**
+ * Verifies an x, y position is within the boundaries of an element.
+ */
+const isPositionInsideElement = async (locator: Locator, x: number, y: number) => {
+	const bounding = await getBoundingBox(locator);
+	const isXValid = bounding.x <= x && x <= bounding.x + bounding.width;
+	const isYValid = bounding.y <= y && y <= bounding.y + bounding.height;
+	return isXValid && isYValid;
+};
diff --git a/e2e/dropdown.spec.ts b/e2e/dropdown.spec.ts
index c34dfe3d..8ac3433f 100644
--- a/e2e/dropdown.spec.ts
+++ b/e2e/dropdown.spec.ts
@@ -16,7 +16,7 @@ async function nav(page: Page) {
 
 test.describe.configure({ mode: 'parallel' });
 
-test.describe('menu', () => {
+test.describe.skip('menu', () => {
 	test.beforeEach(async ({ page }) => {
 		await nav(page);
 	});
@@ -40,7 +40,7 @@ test.describe('menu', () => {
 	}
 });
 
-test.describe('first menu item', () => {
+test.describe.skip('first menu item', () => {
 	test.beforeEach(async ({ page }) => {
 		await nav(page);
 	});
diff --git a/e2e/utils.ts b/e2e/utils.ts
index 93e98950..2c0888cc 100644
--- a/e2e/utils.ts
+++ b/e2e/utils.ts
@@ -1,5 +1,10 @@
 import { Page } from '@playwright/test';
 
 export const jsAvailable = async (page: Page) => {
+	if (process.env.CI) {
+		return await page.evaluate(() => {
+			return new Promise((resolve) => setTimeout(resolve, 0));
+		});
+	}
 	await page.waitForSelector('[data-browser]');
 };
diff --git a/src/tests/dialog/Dialog.spec.ts b/src/tests/dialog/Dialog.spec.ts
index c9dedae1..384a138a 100644
--- a/src/tests/dialog/Dialog.spec.ts
+++ b/src/tests/dialog/Dialog.spec.ts
@@ -230,6 +230,30 @@ describe('Dialog', () => {
 		await waitFor(() => expect(content).not.toBeVisible());
 	});
 
+	it('Respects the `closeOnEscape` prop', async () => {
+		const { getByTestId, user, trigger } = setup({
+			closeOnEscape: false,
+		});
+		expect(trigger).toBeVisible();
+		const content = getByTestId('content');
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		expect(content).toBeVisible();
+		await user.keyboard(kbd.ESCAPE);
+		expect(content).toBeVisible();
+	});
+
+	it("Doesn't close on escape if child intercepts event", async () => {
+		const { getByTestId, user, trigger } = setup();
+		await user.click(trigger);
+		const content = getByTestId('content');
+		expect(content).toBeVisible();
+		const input = getByTestId('input-keydown-interceptor');
+		input.focus();
+		await user.keyboard(kbd.ESCAPE);
+		expect(content).toBeVisible();
+	});
+
 	it('Applies custom ids when provided', async () => {
 		const ids = {
 			content: 'id-content',
@@ -277,4 +301,34 @@ describe('Dialog', () => {
 		await user.pointer({ target: overlay, offset: 2, keys: '[/MouseLeft]' });
 		expect(content).not.toBeVisible();
 	});
+
+	it("Doesn't deactivate focus trap on escape provided `closeOnEscape` false", async () => {
+		const { getByTestId, user, trigger } = setup({
+			closeOnEscape: false,
+		});
+		const content = getByTestId('content');
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		expect(content).toBeVisible();
+		await user.keyboard(kbd.ESCAPE);
+		expect(content).toBeVisible();
+		expect(getByTestId('content')).toHaveFocus();
+		await user.tab({ shift: true });
+		expect(getByTestId('floating-closer')).not.toHaveFocus();
+	});
+
+	it("Doesn't deactivate focus trap on outside click provided `closeOnOutsideClick` false", async () => {
+		const { getByTestId, user, trigger } = setup({
+			closeOnOutsideClick: false,
+		});
+		const content = getByTestId('content');
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		expect(content).toBeVisible();
+		await user.click(getByTestId('overlay'));
+		expect(content).toBeVisible();
+		expect(getByTestId('content')).toHaveFocus();
+		await user.tab({ shift: true });
+		expect(getByTestId('floating-closer')).not.toHaveFocus();
+	});
 });
diff --git a/src/tests/dialog/DialogNestedTest.svelte b/src/tests/dialog/DialogNestedTest.svelte
index 110398c8..ac5f2820 100644
--- a/src/tests/dialog/DialogNestedTest.svelte
+++ b/src/tests/dialog/DialogNestedTest.svelte
@@ -22,8 +22,8 @@
 
 <main>
 	<button use:melt={$trigger} data-testid="trigger">Open</button>
-	<div use:melt={$portalled} data-testid="portalled">
-		{#if $open}
+	{#if $open}
+		<div use:melt={$portalled} data-testid="portalled">
 			<div use:melt={$overlay} data-testid="overlay" transition:fade />
 			<div use:melt={$content} data-testid="content">
 				<h2 use:melt={$title}>Title</h2>
@@ -31,8 +31,8 @@
 
 				<button use:melt={$close} data-testid="closer">Close</button>
 				<button use:melt={$triggerA} data-testid="triggerA">Close2</button>
-				<div use:melt={$portalledA} data-testid="portalledA">
-					{#if $openA}
+				{#if $openA}
+					<div use:melt={$portalledA} data-testid="portalledA">
 						<div use:melt={$overlayA} data-testid="overlayA" />
 						<div use:melt={$contentA} data-testid="contentA" transition:fade>
 							<h2 use:melt={$titleA}>Title</h2>
@@ -41,11 +41,11 @@
 							<button use:melt={$closeA} data-testid="closerA">Close</button>
 							<button use:melt={$closeA} data-testid="lastA">Close2</button>
 						</div>
-					{/if}
-				</div>
+					</div>
+				{/if}
 			</div>
-		{/if}
-	</div>
+		</div>
+	{/if}
 </main>
 
 <style>
diff --git a/src/tests/dialog/DialogTest.svelte b/src/tests/dialog/DialogTest.svelte
index 0c0b213a..60d8e2ea 100644
--- a/src/tests/dialog/DialogTest.svelte
+++ b/src/tests/dialog/DialogTest.svelte
@@ -1,5 +1,6 @@
 <script lang="ts">
 	import { createDialog, melt, type CreateDialogProps } from '$lib/index.js';
+	import { kbd } from '$lib/internal/helpers/keyboard.js';
 
 	type $$Props = CreateDialogProps;
 
@@ -19,6 +20,11 @@
 		<div use:melt={$content} data-testid="content">
 			<h2 use:melt={$title} data-testid="title">Title</h2>
 			<p use:melt={$description} data-testid="description">Description</p>
+			<input
+				data-testid="input-keydown-interceptor"
+				type="text"
+				on:keydown={(e) => e.key === kbd.ESCAPE && e.stopPropagation()}
+			/>
 
 			<button use:melt={$close} data-testid="closer">Close</button>
 			<button use:melt={$close} data-testid="last">Close</button>
diff --git a/src/tests/popover/Popover.spec.ts b/src/tests/popover/Popover.spec.ts
index 676c4f24..7d1f5664 100644
--- a/src/tests/popover/Popover.spec.ts
+++ b/src/tests/popover/Popover.spec.ts
@@ -135,4 +135,33 @@ describe('Popover (Default)', () => {
 		expect(trigger.id).toBe(ids.trigger);
 		expect(content.id).toBe(ids.content);
 	});
+
+	it("Doesn't deactivate focus trap on escape provided `closeOnEscape` false", async () => {
+		const { getByTestId, user, trigger } = setup({
+			closeOnEscape: false,
+		});
+		const content = getByTestId('content');
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		expect(content).toBeVisible();
+		await user.keyboard(kbd.ESCAPE);
+		expect(content).toBeVisible();
+		expect(getByTestId('content')).toHaveFocus();
+		await user.tab({ shift: true });
+		expect(getByTestId('trigger')).not.toHaveFocus();
+	});
+
+	it("Doesn't deactivate focus trap on outside click provided `closeOnOutsideClick` false", async () => {
+		const { getByTestId, user, trigger } = setup({
+			closeOnOutsideClick: false,
+		});
+		const content = getByTestId('content');
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		expect(content).toBeVisible();
+		await user.click(getByTestId('outside'));
+		expect(content).toBeVisible();
+		await user.tab();
+		expect(getByTestId('closeFocus')).not.toHaveFocus();
+	});
 });
diff --git a/src/tests/slider/Slider.spec.ts b/src/tests/slider/Slider.spec.ts
index df42592f..d6f99ba7 100644
--- a/src/tests/slider/Slider.spec.ts
+++ b/src/tests/slider/Slider.spec.ts
@@ -1,4 +1,4 @@
-import { kbd } from '$lib/internal/helpers/index.js';
+import { testKbd as kbd } from '../utils.js';
 import { act, render } from '@testing-library/svelte';
 import { userEvent } from '@testing-library/user-event';
 import { axe } from 'jest-axe';
@@ -89,7 +89,7 @@ describe('Slider (Default)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${key}}`);
+		await user.keyboard(key);
 
 		expectPercentage({ percentage: 31, thumb, range });
 	});
@@ -102,7 +102,7 @@ describe('Slider (Default)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${key}}`);
+		await user.keyboard(key);
 
 		expectPercentage({ percentage: 29, thumb, range });
 	});
@@ -115,7 +115,7 @@ describe('Slider (Default)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${kbd.HOME}}`);
+		await user.keyboard(kbd.HOME);
 
 		expectPercentage({ percentage: 0, thumb, range });
 	});
@@ -127,7 +127,7 @@ describe('Slider (Default)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${kbd.END}}`);
+		await user.keyboard(kbd.END);
 
 		expectPercentage({ percentage: 100, thumb, range });
 	});
@@ -172,7 +172,7 @@ describe('Slider (Range)', () => {
 			const range = getByTestId('range');
 
 			await act(() => thumb0.focus());
-			await user.keyboard(`{${key}}`);
+			await user.keyboard(key);
 
 			expectPercentages({ percentages: [21, 80], thumbs: [thumb0, thumb1], range });
 		}
@@ -189,7 +189,7 @@ describe('Slider (Range)', () => {
 			const range = getByTestId('range');
 
 			await act(() => thumb1.focus());
-			await user.keyboard(`{${key}}`);
+			await user.keyboard(key);
 
 			expectPercentages({ percentages: [20, 81], thumbs: [thumb0, thumb1], range });
 		}
@@ -206,7 +206,7 @@ describe('Slider (Range)', () => {
 			const range = getByTestId('range');
 
 			await act(() => thumb0.focus());
-			await user.keyboard(`{${key}}`);
+			await user.keyboard(key);
 
 			expectPercentages({ percentages: [19, 80], thumbs: [thumb0, thumb1], range });
 		}
@@ -223,7 +223,7 @@ describe('Slider (Range)', () => {
 			const range = getByTestId('range');
 
 			await act(() => thumb1.focus());
-			await user.keyboard(`{${key}}`);
+			await user.keyboard(key);
 
 			expectPercentages({ percentages: [20, 79], thumbs: [thumb0, thumb1], range });
 		}
@@ -242,9 +242,9 @@ describe('Slider (Range)', () => {
 			const range = getByTestId('range');
 
 			await act(() => thumb0.focus());
-			await user.keyboard(`{${key}}`);
-			await user.keyboard(`{${key}}`);
-			await user.keyboard(`{${key}}`);
+			await user.keyboard(key);
+			await user.keyboard(key);
+			await user.keyboard(key);
 
 			expectPercentages({ percentages: [51, 52], thumbs: [thumb0, thumb1], range });
 			expect(thumb1).toHaveFocus();
@@ -264,9 +264,9 @@ describe('Slider (Range)', () => {
 			const range = getByTestId('range');
 
 			await act(() => thumb1.focus());
-			await user.keyboard(`{${key}}`);
-			await user.keyboard(`{${key}}`);
-			await user.keyboard(`{${key}}`);
+			await user.keyboard(key);
+			await user.keyboard(key);
+			await user.keyboard(key);
 
 			expectPercentages({ percentages: [48, 49], thumbs: [thumb0, thumb1], range });
 			expect(thumb0).toHaveFocus();
@@ -282,7 +282,7 @@ describe('Slider (Range)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb0.focus());
-		await user.keyboard(`{${kbd.HOME}}`);
+		await user.keyboard(kbd.HOME);
 
 		expectPercentages({ percentages: [0, 80], thumbs: [thumb0, thumb1], range });
 	});
@@ -296,7 +296,7 @@ describe('Slider (Range)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb1.focus());
-		await user.keyboard(`{${kbd.END}}`);
+		await user.keyboard(kbd.END);
 
 		expectPercentages({ percentages: [20, 100], thumbs: [thumb0, thumb1], range });
 	});
@@ -310,7 +310,7 @@ describe('Slider (Range)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb1.focus());
-		await user.keyboard(`{${kbd.HOME}}`);
+		await user.keyboard(kbd.HOME);
 
 		expectPercentages({ percentages: [0, 20], thumbs: [thumb0, thumb1], range });
 		expect(thumb0).toHaveFocus();
@@ -325,7 +325,7 @@ describe('Slider (Range)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb0.focus());
-		await user.keyboard(`{${kbd.END}}`);
+		await user.keyboard(kbd.END);
 
 		expectPercentages({ percentages: [80, 100], thumbs: [thumb0, thumb1], range });
 		expect(thumb1).toHaveFocus();
@@ -361,7 +361,7 @@ describe('Slider (Small min, max, step)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${key}}`);
+		await user.keyboard(key);
 
 		expectPercentage({ percentage: 51, thumb, range });
 	});
@@ -379,7 +379,7 @@ describe('Slider (Small min, max, step)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${key}}`);
+		await user.keyboard(key);
 
 		expectPercentage({ percentage: 49, thumb, range });
 	});
@@ -414,7 +414,7 @@ describe('Slider (negative min)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${key}}`);
+		await user.keyboard(key);
 
 		expectPercentage({ percentage: 51, thumb, range });
 	});
@@ -432,7 +432,7 @@ describe('Slider (negative min)', () => {
 		const range = getByTestId('range');
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${key}}`);
+		await user.keyboard(key);
 
 		expectPercentage({ percentage: 49, thumb, range });
 	});
@@ -581,10 +581,10 @@ describe('Slider (min=0, max=100, step=30)', () => {
 		expect(thumb).toBeInTheDocument();
 
 		await act(() => thumb.focus());
-		await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
-		await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
-		await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
-		await user.keyboard(`{${kbd.ARROW_RIGHT}}`);
+		await user.keyboard(kbd.ARROW_RIGHT);
+		await user.keyboard(kbd.ARROW_RIGHT);
+		await user.keyboard(kbd.ARROW_RIGHT);
+		await user.keyboard(kbd.ARROW_RIGHT);
 
 		expectPercentage({ percentage: 90, thumb, range: getByTestId('range') });
 	});
diff --git a/src/tests/switch/Switch.spec.ts b/src/tests/switch/Switch.spec.ts
index 72fa80d3..f2a31754 100644
--- a/src/tests/switch/Switch.spec.ts
+++ b/src/tests/switch/Switch.spec.ts
@@ -3,7 +3,7 @@ import { axe } from 'jest-axe';
 import { describe, it } from 'vitest';
 import SwitchTest from './SwitchTest.svelte';
 import { userEvent } from '@testing-library/user-event';
-import { kbd } from '$lib/internal/helpers/index.js';
+import { testKbd as kbd } from '../utils.js';
 import { writable } from 'svelte/store';
 import { tick } from 'svelte';
 
@@ -35,11 +35,11 @@ describe('Switch', () => {
 		expect(switchEl).toHaveFocus();
 
 		for (const key of keys) {
-			await userEvent.keyboard(`{${key}}`);
+			await userEvent.keyboard(key);
 			expect(switchEl).toHaveAttribute('data-state', 'checked');
 			expect(switchEl).toHaveAttribute('aria-checked', 'true');
 
-			await userEvent.keyboard(`{${key}}`);
+			await userEvent.keyboard(key);
 			expect(switchEl).toHaveAttribute('data-state', 'unchecked');
 			expect(switchEl).toHaveAttribute('aria-checked', 'false');
 		}
diff --git a/src/tests/tabs/Tabs.spec.ts b/src/tests/tabs/Tabs.spec.ts
index 488ec116..8da38cad 100644
--- a/src/tests/tabs/Tabs.spec.ts
+++ b/src/tests/tabs/Tabs.spec.ts
@@ -1,4 +1,4 @@
-import { kbd } from '$lib/internal/helpers/keyboard.js';
+import { testKbd as kbd } from '../utils.js';
 import { render } from '@testing-library/svelte';
 import { userEvent } from '@testing-library/user-event';
 import { axe } from 'jest-axe';
@@ -34,7 +34,7 @@ describe('Tabs', () => {
 
 		for (const tab of tabs) {
 			expect(getByTestId(`${tab}-trigger`)).toHaveFocus();
-			await userEvent.keyboard(`{${kbd.ARROW_RIGHT}}`);
+			await userEvent.keyboard(kbd.ARROW_RIGHT);
 		}
 
 		const lastTrigger = getByTestId(`${tabs[tabs.length - 1]}-trigger`);
@@ -42,7 +42,7 @@ describe('Tabs', () => {
 
 		for (const tab of [...tabs].reverse()) {
 			expect(getByTestId(`${tab}-trigger`)).toHaveFocus();
-			await userEvent.keyboard(`{${kbd.ARROW_LEFT}}`);
+			await userEvent.keyboard(kbd.ARROW_LEFT);
 		}
 	});
 });
diff --git a/src/tests/tags-input/TagsInput.spec.ts b/src/tests/tags-input/TagsInput.spec.ts
index dd58ce49..2ffd97f3 100644
--- a/src/tests/tags-input/TagsInput.spec.ts
+++ b/src/tests/tags-input/TagsInput.spec.ts
@@ -1,4 +1,4 @@
-import { kbd } from '$lib/internal/helpers/index.js';
+import { testKbd as kbd } from '../utils.js';
 import { queryByText, render } from '@testing-library/svelte';
 import { userEvent } from '@testing-library/user-event';
 import { axe } from 'jest-axe';
@@ -33,7 +33,7 @@ describe('TagsInput', () => {
 		const input = await findByRole('textbox');
 		const user = userEvent.setup();
 		const newTag = 'new tag';
-		await user.type(input, `${newTag}{${kbd.ENTER}}`);
+		await user.type(input, `${newTag}${kbd.ENTER}`);
 		expect(input).not.toHaveValue();
 		expect(queryByText(newTag)).toBeInTheDocument();
 	});
@@ -49,7 +49,7 @@ describe('TagsInput', () => {
 
 		const input = await findByRole('textbox');
 		const user = userEvent.setup();
-		await user.type(input, `${newTag}{${kbd.ENTER}}`);
+		await user.type(input, `${newTag}${kbd.ENTER}`);
 		expect(input).toHaveValue();
 	});
 
@@ -68,7 +68,7 @@ describe('TagsInput', () => {
 
 		const input = await findByRole('textbox');
 		const user = userEvent.setup();
-		await user.type(input, `${newTag}   {${kbd.ENTER}}`);
+		await user.type(input, `${newTag}   ${kbd.ENTER}`);
 		expect(input).toHaveValue();
 	});
 
@@ -82,7 +82,7 @@ describe('TagsInput', () => {
 
 		const input = await findByRole('textbox');
 		const user = userEvent.setup();
-		await user.type(input, `${newTag}   {${kbd.ENTER}}`);
+		await user.type(input, `${newTag}   ${kbd.ENTER}`);
 		expect(input).not.toHaveValue();
 		expect(queryByText(newTag)).toBeInTheDocument();
 	});
@@ -99,7 +99,7 @@ describe('TagsInput', () => {
 
 		const input = await findByRole('textbox');
 		const user = userEvent.setup();
-		await user.type(input, `${newTag}   {${kbd.ENTER}}`);
+		await user.type(input, `${newTag}   ${kbd.ENTER}`);
 		expect(input).toHaveValue();
 	});
 
@@ -115,10 +115,10 @@ describe('TagsInput', () => {
 
 		const input = await findByRole('textbox');
 		const user = userEvent.setup();
-		await user.type(input, `${newTag}{${kbd.ENTER}}`);
+		await user.type(input, `${newTag}${kbd.ENTER}`);
 		expect(input).toHaveValue();
 		user.clear(input);
-		await user.type(input, `${allowedTag}{${kbd.ENTER}}`);
+		await user.type(input, `${allowedTag}${kbd.ENTER}`);
 		expect(input).not.toHaveValue();
 		expect(queryByText(allowedTag)).toBeInTheDocument();
 	});
@@ -135,10 +135,10 @@ describe('TagsInput', () => {
 
 		const input = await findByRole('textbox');
 		const user = userEvent.setup();
-		await user.type(input, `${newTag}   {${kbd.ENTER}}`);
+		await user.type(input, `${newTag}   ${kbd.ENTER}`);
 		expect(input).toHaveValue();
 		user.clear(input);
-		await user.type(input, `${allowedTag}    {${kbd.ENTER}}`);
+		await user.type(input, `${allowedTag}    ${kbd.ENTER}`);
 		expect(input).not.toHaveValue();
 		expect(queryByText(allowedTag)).toBeInTheDocument();
 	});
@@ -157,10 +157,10 @@ describe('TagsInput', () => {
 
 		const input = await findByRole('textbox');
 		const user = userEvent.setup();
-		await user.type(input, `${newTag}   {${kbd.ENTER}}`);
+		await user.type(input, `${newTag}   ${kbd.ENTER}`);
 		expect(input).toHaveValue();
 		user.clear(input);
-		await user.type(input, `${allowedTag}    {${kbd.ENTER}}`);
+		await user.type(input, `${allowedTag}    ${kbd.ENTER}`);
 		expect(input).not.toHaveValue();
 		expect(queryByText(allowedTag)).toBeInTheDocument();
 	});
diff --git a/src/tests/toolbar/Toolbar.spec.ts b/src/tests/toolbar/Toolbar.spec.ts
index 1f93f658..cd013dc1 100644
--- a/src/tests/toolbar/Toolbar.spec.ts
+++ b/src/tests/toolbar/Toolbar.spec.ts
@@ -1,4 +1,4 @@
-import { kbd } from '$lib/internal/helpers/keyboard.js';
+import { testKbd as kbd } from '../utils.js';
 import { render } from '@testing-library/svelte';
 import { userEvent } from '@testing-library/user-event';
 import { axe } from 'jest-axe';
@@ -113,8 +113,8 @@ describe('Toolbar', () => {
 		const user = userEvent.setup();
 		const { getByTestId } = render(ToolbarTest, props);
 
-		const nextKey = `{${props.orientation === horizontal ? kbd.ARROW_RIGHT : kbd.ARROW_DOWN}}`;
-		const prevKey = `{${props.orientation === horizontal ? kbd.ARROW_LEFT : kbd.ARROW_UP}}`;
+		const nextKey = props.orientation === horizontal ? kbd.ARROW_RIGHT : kbd.ARROW_DOWN;
+		const prevKey = props.orientation === horizontal ? kbd.ARROW_LEFT : kbd.ARROW_UP;
 
 		const reversedItems = [...items].reverse();
 
@@ -180,8 +180,8 @@ describe('Toolbar', () => {
 			const user = userEvent.setup();
 			const { getByTestId } = render(ToolbarTest, { loop: true, ...props });
 
-			const nextKey = `{${props.orientation === horizontal ? kbd.ARROW_RIGHT : kbd.ARROW_DOWN}}`;
-			const prevKey = `{${props.orientation === horizontal ? kbd.ARROW_LEFT : kbd.ARROW_UP}}`;
+			const nextKey = props.orientation === horizontal ? kbd.ARROW_RIGHT : kbd.ARROW_DOWN;
+			const prevKey = props.orientation === horizontal ? kbd.ARROW_LEFT : kbd.ARROW_UP;
 
 			await user.tab();
 			const firstItem = getByTestId(items[0]);
@@ -243,8 +243,8 @@ describe('Toolbar', () => {
 			const user = userEvent.setup();
 			const { getByTestId } = render(ToolbarTest, { loop: false, ...props });
 
-			const nextKey = `{${props.orientation === horizontal ? kbd.ARROW_RIGHT : kbd.ARROW_DOWN}}`;
-			const prevKey = `{${props.orientation === horizontal ? kbd.ARROW_LEFT : kbd.ARROW_UP}}`;
+			const nextKey = props.orientation === horizontal ? kbd.ARROW_RIGHT : kbd.ARROW_DOWN;
+			const prevKey = props.orientation === horizontal ? kbd.ARROW_LEFT : kbd.ARROW_UP;
 
 			const firstItem = getByTestId(items[0]);
 			const lastItem = getByTestId(items[items.length - 1]);
@@ -312,9 +312,9 @@ describe('Toolbar', () => {
 		const firstItem = getByTestId(items[0]);
 		const lastItem = getByTestId(items[items.length - 1]);
 		expect(firstItem).toHaveFocus();
-		await user.keyboard(`{${kbd.END}}`);
+		await user.keyboard(kbd.END);
 		expect(lastItem).toHaveFocus();
-		await user.keyboard(`{${kbd.HOME}}`);
+		await user.keyboard(kbd.HOME);
 		expect(firstItem).toHaveFocus();
 	});
 
@@ -326,7 +326,7 @@ describe('Toolbar', () => {
 			const curr = getByTestId(item);
 			curr.focus();
 			expect(curr).toHaveFocus();
-			await user.keyboard(`{${kbd.SPACE}}`);
+			await user.keyboard(kbd.SPACE);
 			expect(curr).toHaveAttribute('aria-pressed', 'true');
 			expect(curr).toHaveAttribute('data-state', 'on');
 		}
@@ -335,7 +335,7 @@ describe('Toolbar', () => {
 			const curr = getByTestId(item);
 			curr.focus();
 			expect(curr).toHaveFocus();
-			await user.keyboard(`{${kbd.SPACE}}`);
+			await user.keyboard(kbd.SPACE);
 			expect(curr).toHaveAttribute('aria-checked', 'true');
 			expect(curr).toHaveAttribute('data-state', 'on');
 		}