Repository: melt-ui/melt-ui
Pull request #236 (2023-07-17 01:40:39)

The Big Bad - Part 1: Builder Return Types
- [x] Add a wrapper to handle creating an object of writable option stores
- [x] Update return object structure of all builders
- [x] Update examples/internal comps to use new return types 
- [x] Export types for `elements`, `options`, `actions`, `helpers`, and `states` returned from builders.


Patch:
diff --git a/package.json b/package.json
index ca95d65b..0726e90d 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
 		"@huntabyte/mdsvex": "0.16.5",
 		"@iconify-json/lucide": "^1.1.104",
 		"@iconify-json/simple-icons": "^1.1.57",
-		"@melt-ui/pp": "^0.0.2",
+		"@melt-ui/pp": "^0.0.5",
 		"@playwright/test": "^1.36.0",
 		"@storybook/addon-essentials": "^7.0.23",
 		"@storybook/addon-interactions": "^7.0.23",
@@ -105,6 +105,7 @@
 		"highlight.js": "^11.8.0",
 		"jest-axe": "^7.0.1",
 		"jsdom": "^22.1.0",
+		"lucide-svelte": "^0.260.0",
 		"postcss": "^8.4.24",
 		"postcss-load-config": "^4.0.1",
 		"prettier": "2.8.7",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 35de7ac0..9af1355a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,8 +32,8 @@ devDependencies:
     specifier: ^1.1.57
     version: 1.1.57
   '@melt-ui/pp':
-    specifier: ^0.0.2
-    version: 0.0.2(svelte@4.0.0)
+    specifier: ^0.0.5
+    version: 0.0.5(@melt-ui/svelte@0.22.2)(svelte@4.0.0)
   '@playwright/test':
     specifier: ^1.36.0
     version: 1.36.0
@@ -142,6 +142,9 @@ devDependencies:
   jsdom:
     specifier: ^22.1.0
     version: 22.1.0
+  lucide-svelte:
+    specifier: ^0.260.0
+    version: 0.260.0(svelte@4.0.0)
   postcss:
     specifier: ^8.4.24
     version: 8.4.24
@@ -3079,13 +3082,11 @@ packages:
 
   /@floating-ui/core@1.3.1:
     resolution: {integrity: sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==}
-    dev: false
 
   /@floating-ui/dom@1.4.2:
     resolution: {integrity: sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==}
     dependencies:
       '@floating-ui/core': 1.3.1
-    dev: false
 
   /@humanwhocodes/config-array@0.11.10:
     resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
@@ -3328,11 +3329,25 @@ packages:
       react: 18.2.0
     dev: true
 
-  /@melt-ui/pp@0.0.2(svelte@4.0.0):
-    resolution: {integrity: sha512-OBxsoe3SI9YEWGgQVnxuaIpCt/ag5KhE47b+ttTycazldfXlB9Yfbs6+6kYvIkZ8SMS74ZXu0zIVs3iZVxxMmQ==}
+  /@melt-ui/pp@0.0.5(@melt-ui/svelte@0.22.2)(svelte@4.0.0):
+    resolution: {integrity: sha512-KFfZXn70wHzEX8jkGy0p+MJpPuv1HS8THF3glZI5kuOGWSvLjVoxtuNhk5UuJSzsNk0OlIlVaAZIRSPrswE5Gw==}
     peerDependencies:
+      '@melt-ui/svelte': ^0.22.0
       svelte: ^3.55.0 || ^4.0.0
     dependencies:
+      '@melt-ui/svelte': 0.22.2(svelte@4.0.0)
+      svelte: 4.0.0
+    dev: true
+
+  /@melt-ui/svelte@0.22.2(svelte@4.0.0):
+    resolution: {integrity: sha512-mcXyoEW5/74y3QWnxHeSZgj+F/zG6mK4F//Wqig3KUGZ6uUEDwjkRCGDhY3EvL5n8MfY2P8uJWXiTZ16kLeh7g==}
+    peerDependencies:
+      svelte: '>=3 <5'
+    dependencies:
+      '@floating-ui/core': 1.3.1
+      '@floating-ui/dom': 1.4.2
+      focus-trap: 7.4.3
+      nanoid: 4.0.2
       svelte: 4.0.0
     dev: true
 
@@ -7591,7 +7606,6 @@ packages:
     resolution: {integrity: sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==}
     dependencies:
       tabbable: 6.1.2
-    dev: false
 
   /for-each@0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -9170,6 +9184,14 @@ packages:
     engines: {node: '>=12'}
     dev: true
 
+  /lucide-svelte@0.260.0(svelte@4.0.0):
+    resolution: {integrity: sha512-fvR/42lZdIaW9RCVIouIMO9LgxwBcE4M770Hpl1ITL2At5YamgQ8J/652mTBXnX7qfiTCND/mSVnnXGESypaEw==}
+    peerDependencies:
+      svelte: '>=3 <5'
+    dependencies:
+      svelte: 4.0.0
+    dev: true
+
   /lz-string@1.5.0:
     resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
     hasBin: true
@@ -9849,7 +9871,6 @@ packages:
     resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
     engines: {node: ^14 || ^16 || >=18}
     hasBin: true
-    dev: false
 
   /natural-compare-lite@1.4.0:
     resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
@@ -11946,7 +11967,6 @@ packages:
 
   /tabbable@6.1.2:
     resolution: {integrity: sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ==}
-    dev: false
 
   /tailwind-merge@1.13.2:
     resolution: {integrity: sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==}
diff --git a/src/docs/components/code-block.svelte b/src/docs/components/code-block.svelte
index 802eac4d..16edfd03 100644
--- a/src/docs/components/code-block.svelte
+++ b/src/docs/components/code-block.svelte
@@ -1,6 +1,5 @@
 <script lang="ts">
-	import Check from '~icons/lucide/check';
-	import Copy from '~icons/lucide/copy';
+	import { Check, Copy } from 'lucide-svelte';
 	import { fly } from 'svelte/transition';
 	import { cn, createCopyCodeButton } from '$docs/utils';
 
diff --git a/src/docs/components/construction.svelte b/src/docs/components/construction.svelte
index 0a7da984..432074eb 100644
--- a/src/docs/components/construction.svelte
+++ b/src/docs/components/construction.svelte
@@ -1,5 +1,5 @@
 <script lang="ts">
-	import Construction from '~icons/lucide/construction';
+	import { Construction } from 'lucide-svelte';
 	import { a as A } from '$docs/components';
 </script>
 
diff --git a/src/docs/components/info-popover.svelte b/src/docs/components/info-popover.svelte
index 73a78ac0..4bfc779c 100644
--- a/src/docs/components/info-popover.svelte
+++ b/src/docs/components/info-popover.svelte
@@ -2,9 +2,12 @@
 	import { cn } from '$docs/utils';
 	import { createPopover } from '@melt-ui/svelte';
 	import { fade } from 'svelte/transition';
-	import Info from '~icons/lucide/info';
+	import { Info } from 'lucide-svelte';
 
-	const { trigger, content, open, arrow } = createPopover();
+	const {
+		elements: { trigger, content, arrow },
+		states: { open },
+	} = createPopover();
 
 	export let iconClasses = '';
 	export let contentClasses = '';
diff --git a/src/docs/components/markdown/a.svelte b/src/docs/components/markdown/a.svelte
index 30d066d6..26508058 100644
--- a/src/docs/components/markdown/a.svelte
+++ b/src/docs/components/markdown/a.svelte
@@ -1,5 +1,5 @@
 <script lang="ts">
-	import External from '~icons/lucide/external-link';
+	import { ExternalLink } from 'lucide-svelte';
 
 	export let href: string;
 
@@ -17,6 +17,6 @@
 >
 	<slot />
 	{#if !internal}
-		<External class="h-4 w-4" />
+		<ExternalLink class="h-4 w-4" />
 	{/if}
 </a>
diff --git a/src/docs/components/markdown/pre.svelte b/src/docs/components/markdown/pre.svelte
index 2dab5d96..406158bb 100644
--- a/src/docs/components/markdown/pre.svelte
+++ b/src/docs/components/markdown/pre.svelte
@@ -2,8 +2,7 @@
 	import { cn, createCopyCodeButton } from '$docs/utils';
 	import { usingPreprocessor } from '$routes/store';
 	import { fly } from 'svelte/transition';
-	import Check from '~icons/lucide/check';
-	import Copy from '~icons/lucide/copy';
+	import { Check, Copy } from 'lucide-svelte';
 
 	let className: string | undefined | null = undefined;
 	export { className as class };
diff --git a/src/docs/components/nav/mobile-nav.svelte b/src/docs/components/nav/mobile-nav.svelte
index 52e927ae..99741784 100644
--- a/src/docs/components/nav/mobile-nav.svelte
+++ b/src/docs/components/nav/mobile-nav.svelte
@@ -1,14 +1,17 @@
 <script lang="ts">
 	import { createDialog } from '@melt-ui/svelte';
 	import { fade, fly } from 'svelte/transition';
-	import Menu from '~icons/lucide/menu';
-	import X from '~icons/lucide/x';
+	import { Menu, X } from 'lucide-svelte';
 	import { Button, MobileNavLink } from '$docs/components';
 	import { navConfig } from '$docs/config';
 	import Switch from '../switch.svelte';
 	import { usingPreprocessor } from '$routes/store';
 
-	const { trigger, portal, overlay, content, close, open } = createDialog();
+	const {
+		elements: { trigger, overlay, content, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 </script>
 
 <button
diff --git a/src/docs/components/preview-style-select.svelte b/src/docs/components/preview-style-select.svelte
index 83144044..5693e899 100644
--- a/src/docs/components/preview-style-select.svelte
+++ b/src/docs/components/preview-style-select.svelte
@@ -1,7 +1,6 @@
 <script lang="ts">
 	import { createSelect, type SelectOptionProps } from '$lib';
-	import ChevronDown from '~icons/lucide/chevron-down';
-	import Check from '~icons/lucide/check';
+	import { ChevronDown, Check } from 'lucide-svelte';
 	import { beforeNavigate } from '$app/navigation';
 	import type { Writable } from 'svelte/store';
 
@@ -9,15 +8,10 @@
 	export let codingStyle: Writable<'tailwind' | 'css'>;
 
 	const {
-		value: localValue,
-		label,
-		trigger,
-		menu,
-		option,
-		isSelected,
-	} = createSelect({
-		value: $codingStyle,
-	});
+		elements: { trigger, menu, option },
+		states: { value: localValue, label },
+		helpers: { isSelected },
+	} = createSelect({ value: $codingStyle });
 
 	localValue.subscribe((v) => {
 		if (v === 'tailwind' || v === 'css') {
diff --git a/src/docs/components/sections/features.svelte b/src/docs/components/sections/features.svelte
index 8a3c697f..f4adb166 100644
--- a/src/docs/components/sections/features.svelte
+++ b/src/docs/components/sections/features.svelte
@@ -1,5 +1,5 @@
 <script lang="ts">
-	import CheckCircle from '~icons/lucide/check-circle';
+	import { CheckCircle } from 'lucide-svelte';
 	import { h2 as H2 } from '$docs/components/markdown';
 	export let features: string[];
 </script>
diff --git a/src/docs/components/switch.svelte b/src/docs/components/switch.svelte
index a8145f6a..d79a605d 100644
--- a/src/docs/components/switch.svelte
+++ b/src/docs/components/switch.svelte
@@ -8,9 +8,9 @@
 	export let keepState = false;
 
 	const {
-		root,
-		isChecked,
-		checked: checkedStore,
+		elements: { root },
+		helpers: { isChecked },
+		states: { checked: checkedStore },
 	} = createSwitch({
 		checked,
 	});
diff --git a/src/docs/components/tabs/root.svelte b/src/docs/components/tabs/root.svelte
index 05629c1b..7ec993bf 100644
--- a/src/docs/components/tabs/root.svelte
+++ b/src/docs/components/tabs/root.svelte
@@ -1,5 +1,8 @@
 <script lang="ts" context="module">
-	type TabsContext = Pick<ReturnType<typeof createTabs>, 'content' | 'list' | 'trigger'> & {
+	type CreateTabs = ReturnType<typeof createTabs>;
+	type Elements = CreateTabs['elements'];
+
+	type TabsContext = Pick<Elements, 'content' | 'list' | 'trigger'> & {
 		tabs: Writable<string[]>;
 	};
 
@@ -17,7 +20,10 @@
 
 	export let tabs: string[] = [];
 
-	const { root, content, list, trigger, value } = createTabs({
+	const {
+		elements: { root, content, list, trigger },
+		states: { value },
+	} = createTabs({
 		value: tabs[0],
 	});
 
diff --git a/src/docs/components/type-dialog.svelte b/src/docs/components/type-dialog.svelte
index f2625139..21851a58 100644
--- a/src/docs/components/type-dialog.svelte
+++ b/src/docs/components/type-dialog.svelte
@@ -1,10 +1,14 @@
 <script lang="ts">
 	import { createDialog } from '$lib/builders';
 	import { flyAndScale } from '$docs/utils';
-	import X from '~icons/lucide/x';
+	import { X } from 'lucide-svelte';
 	import { CodeBlock } from '$docs/components';
 
-	const { trigger, portal, overlay, content, title, close, open } = createDialog({});
+	const {
+		elements: { trigger, overlay, content, title, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 
 	export let name = '';
 	export let code = '';
diff --git a/src/docs/previews/accordion/disabled/css.svelte b/src/docs/previews/accordion/disabled/css.svelte
index 52ec02f0..3662d001 100644
--- a/src/docs/previews/accordion/disabled/css.svelte
+++ b/src/docs/previews/accordion/disabled/css.svelte
@@ -2,7 +2,10 @@
 	import { createAccordion } from '@melt-ui/svelte';
 	import { slide } from 'svelte/transition';
 
-	const { content, item, trigger, isSelected, root } = createAccordion();
+	const {
+		elements: { root, content, item, trigger },
+		helpers: { isSelected },
+	} = createAccordion();
 
 	const items = [
 		{
diff --git a/src/docs/previews/accordion/disabled/tailwind.svelte b/src/docs/previews/accordion/disabled/tailwind.svelte
index d97073a7..12bfc813 100644
--- a/src/docs/previews/accordion/disabled/tailwind.svelte
+++ b/src/docs/previews/accordion/disabled/tailwind.svelte
@@ -2,7 +2,10 @@
 	import { createAccordion } from '@melt-ui/svelte';
 	import { slide } from 'svelte/transition';
 
-	const { content, item, trigger, isSelected, root } = createAccordion();
+	const {
+		elements: { content, item, trigger, root },
+		helpers: { isSelected },
+	} = createAccordion();
 
 	const items = [
 		{
diff --git a/src/docs/previews/accordion/main/css.svelte b/src/docs/previews/accordion/main/css.svelte
index 11c6d56d..a730e9d5 100644
--- a/src/docs/previews/accordion/main/css.svelte
+++ b/src/docs/previews/accordion/main/css.svelte
@@ -2,7 +2,10 @@
 	import { createAccordion } from '@melt-ui/svelte';
 	import { slide } from 'svelte/transition';
 
-	const { content, item, trigger, isSelected, root } = createAccordion();
+	const {
+		elements: { root, content, item, trigger },
+		helpers: { isSelected },
+	} = createAccordion();
 
 	const items = [
 		{
diff --git a/src/docs/previews/accordion/main/tailwind.svelte b/src/docs/previews/accordion/main/tailwind.svelte
index 154f7826..3552cffb 100644
--- a/src/docs/previews/accordion/main/tailwind.svelte
+++ b/src/docs/previews/accordion/main/tailwind.svelte
@@ -2,7 +2,10 @@
 	import { createAccordion } from '@melt-ui/svelte';
 	import { slide } from 'svelte/transition';
 
-	const { content, item, trigger, isSelected, root } = createAccordion();
+	const {
+		elements: { content, item, trigger, root },
+		helpers: { isSelected },
+	} = createAccordion();
 
 	const items = [
 		{
diff --git a/src/docs/previews/accordion/multiple/css.svelte b/src/docs/previews/accordion/multiple/css.svelte
index e3792ed6..ba884efe 100644
--- a/src/docs/previews/accordion/multiple/css.svelte
+++ b/src/docs/previews/accordion/multiple/css.svelte
@@ -2,7 +2,10 @@
 	import { createAccordion } from '@melt-ui/svelte';
 	import { slide } from 'svelte/transition';
 
-	const { content, item, trigger, isSelected, root } = createAccordion({
+	const {
+		elements: { root, content, item, trigger },
+		helpers: { isSelected },
+	} = createAccordion({
 		type: 'multiple',
 	});
 
diff --git a/src/docs/previews/accordion/multiple/tailwind.svelte b/src/docs/previews/accordion/multiple/tailwind.svelte
index 00be35f0..d0243c77 100644
--- a/src/docs/previews/accordion/multiple/tailwind.svelte
+++ b/src/docs/previews/accordion/multiple/tailwind.svelte
@@ -2,7 +2,10 @@
 	import { createAccordion } from '@melt-ui/svelte';
 	import { slide } from 'svelte/transition';
 
-	const { content, item, trigger, isSelected, root } = createAccordion({
+	const {
+		elements: { content, item, trigger, root },
+		helpers: { isSelected },
+	} = createAccordion({
 		type: 'multiple',
 	});
 
diff --git a/src/docs/previews/avatar/main/css.svelte b/src/docs/previews/avatar/main/css.svelte
index 616b949c..f93226d0 100644
--- a/src/docs/previews/avatar/main/css.svelte
+++ b/src/docs/previews/avatar/main/css.svelte
@@ -1,18 +1,24 @@
 <script lang="ts">
 	import { createAvatar } from '@melt-ui/svelte';
 
-	const { image, fallback } = createAvatar({
+	const {
+		elements: { image, fallback },
+	} = createAvatar({
 		src: 'https://avatars.githubusercontent.com/u/1162160?v=4',
 	});
 
 	// With an exaggerated fallback delay
-	const { image: imageA, fallback: fallbackA } = createAvatar({
+	const {
+		elements: { image: imageA, fallback: fallbackA },
+	} = createAvatar({
 		src: 'https://avatars.githubusercontent.com/u/5968653?v=4',
 		delayMs: 650,
 	});
 
 	// A blank source to demonstrate the fallback
-	const { image: imageB, fallback: fallbackB } = createAvatar({
+	const {
+		elements: { image: imageB, fallback: fallbackB },
+	} = createAvatar({
 		src: '',
 	});
 </script>
diff --git a/src/docs/previews/avatar/main/tailwind.svelte b/src/docs/previews/avatar/main/tailwind.svelte
index e63c5485..a6156902 100644
--- a/src/docs/previews/avatar/main/tailwind.svelte
+++ b/src/docs/previews/avatar/main/tailwind.svelte
@@ -1,18 +1,24 @@
 <script lang="ts">
 	import { createAvatar } from '@melt-ui/svelte';
 
-	const { image, fallback } = createAvatar({
+	const {
+		elements: { image, fallback },
+	} = createAvatar({
 		src: 'https://avatars.githubusercontent.com/u/1162160?v=4',
 	});
 
 	// With an exaggerated fallback delay
-	const { image: imageA, fallback: fallbackA } = createAvatar({
+	const {
+		elements: { image: imageA, fallback: fallbackA },
+	} = createAvatar({
 		src: 'https://avatars.githubusercontent.com/u/5968653?v=4',
 		delayMs: 650,
 	});
 
 	// A blank source to demonstrate the fallback
-	const { image: imageB, fallback: fallbackB } = createAvatar({
+	const {
+		elements: { image: imageB, fallback: fallbackB },
+	} = createAvatar({
 		src: '',
 	});
 </script>
diff --git a/src/docs/previews/checkbox/main/css.svelte b/src/docs/previews/checkbox/main/css.svelte
index 6aaffa92..f196ab21 100644
--- a/src/docs/previews/checkbox/main/css.svelte
+++ b/src/docs/previews/checkbox/main/css.svelte
@@ -3,7 +3,10 @@
 	import Check from '~icons/lucide/check';
 	import Minus from '~icons/lucide/minus';
 
-	const { root, input, isChecked, isIndeterminate } = createCheckbox({
+	const {
+		elements: { root, input },
+		helpers: { isChecked, isIndeterminate },
+	} = createCheckbox({
 		checked: 'indeterminate',
 	});
 </script>
diff --git a/src/docs/previews/checkbox/main/tailwind.svelte b/src/docs/previews/checkbox/main/tailwind.svelte
index fffa5ce2..4df75849 100644
--- a/src/docs/previews/checkbox/main/tailwind.svelte
+++ b/src/docs/previews/checkbox/main/tailwind.svelte
@@ -1,9 +1,11 @@
 <script lang="ts">
 	import { createCheckbox } from '$lib';
-	import Check from '~icons/lucide/check';
-	import Minus from '~icons/lucide/minus';
+	import { Check, Minus } from 'lucide-svelte';
 
-	const { root, input, isChecked, isIndeterminate } = createCheckbox({
+	const {
+		elements: { root, input },
+		helpers: { isChecked, isIndeterminate },
+	} = createCheckbox({
 		checked: 'indeterminate',
 	});
 </script>
diff --git a/src/docs/previews/collapsible/main/css.svelte b/src/docs/previews/collapsible/main/css.svelte
index e4a06d49..e67818d7 100644
--- a/src/docs/previews/collapsible/main/css.svelte
+++ b/src/docs/previews/collapsible/main/css.svelte
@@ -4,7 +4,10 @@
 	import ChevronsUpDown from '~icons/lucide/chevrons-up-down';
 	import X from '~icons/lucide/x';
 
-	const { open, root, content, trigger } = createCollapsible();
+	const {
+		elements: { root, content, trigger },
+		states: { open },
+	} = createCollapsible();
 </script>
 
 <div melt={$root} class="root">
diff --git a/src/docs/previews/collapsible/main/tailwind.svelte b/src/docs/previews/collapsible/main/tailwind.svelte
index 58b0ec64..d7ba5b1b 100644
--- a/src/docs/previews/collapsible/main/tailwind.svelte
+++ b/src/docs/previews/collapsible/main/tailwind.svelte
@@ -1,10 +1,12 @@
 <script lang="ts">
 	import { createCollapsible } from '$lib/builders/collapsible';
 	import { slide } from 'svelte/transition';
-	import ChevronsUpDown from '~icons/lucide/chevrons-up-down';
-	import X from '~icons/lucide/x';
+	import { ChevronsUpDown, X } from 'lucide-svelte';
 
-	const { open, root, content, trigger } = createCollapsible();
+	const {
+		elements: { root, content, trigger },
+		states: { open },
+	} = createCollapsible();
 </script>
 
 <div melt={$root} class="mx-auto w-full max-w-md">
diff --git a/src/docs/previews/combobox/main/css.svelte b/src/docs/previews/combobox/main/css.svelte
index 255d0208..f124c655 100644
--- a/src/docs/previews/combobox/main/css.svelte
+++ b/src/docs/previews/combobox/main/css.svelte
@@ -63,21 +63,24 @@
 		},
 	];
 
-	const { open, input, menu, item, inputValue, isSelected, filteredItems } =
-		createCombobox({
-			filterFunction: (item, inputValue) => {
-				// Example string normalization function. Replace as needed.
-				const normalize = (str: string) => str.normalize().toLowerCase();
-				const normalizedInput = normalize(inputValue);
-				return (
-					normalizedInput === '' ||
-					normalize(item.title).includes(normalizedInput) ||
-					normalize(item.author).includes(normalizedInput)
-				);
-			},
-			items: books,
-			itemToString: (item) => item.title,
-		});
+	const {
+		elements: { input, menu, item },
+		states: { open, inputValue, filteredItems },
+		helpers: { isSelected },
+	} = createCombobox({
+		filterFunction: (item, inputValue) => {
+			// Example string normalization function. Replace as needed.
+			const normalize = (str: string) => str.normalize().toLowerCase();
+			const normalizedInput = normalize(inputValue);
+			return (
+				normalizedInput === '' ||
+				normalize(item.title).includes(normalizedInput) ||
+				normalize(item.author).includes(normalizedInput)
+			);
+		},
+		items: books,
+		itemToString: (item) => item.title,
+	});
 </script>
 
 <label>
diff --git a/src/docs/previews/combobox/main/tailwind.svelte b/src/docs/previews/combobox/main/tailwind.svelte
index a3ac157a..979b40bd 100644
--- a/src/docs/previews/combobox/main/tailwind.svelte
+++ b/src/docs/previews/combobox/main/tailwind.svelte
@@ -1,8 +1,6 @@
 <script lang="ts">
-	import { createCombobox } from '@melt-ui/svelte';
-	import Check from '~icons/lucide/check';
-	import ChevronDown from '~icons/lucide/chevron-down';
-	import ChevronUp from '~icons/lucide/chevron-up';
+	import { createCombobox, type ComboboxFilterFunction } from '@melt-ui/svelte';
+	import { Check, ChevronDown, ChevronUp } from 'lucide-svelte';
 
 	interface Book {
 		author: string;
@@ -63,21 +61,25 @@
 		},
 	];
 
-	const { open, input, menu, item, inputValue, isSelected, filteredItems } =
-		createCombobox({
-			filterFunction: (item, inputValue) => {
-				// Example string normalization function. Replace as needed.
-				const normalize = (str: string) => str.normalize().toLowerCase();
-				const normalizedInput = normalize(inputValue);
-				return (
-					normalizedInput === '' ||
-					normalize(item.title).includes(normalizedInput) ||
-					normalize(item.author).includes(normalizedInput)
-				);
-			},
-			items: books,
-			itemToString: (item) => item.title,
-		});
+	const filterFunction: ComboboxFilterFunction<Book> = (item, inputValue) => {
+		// Example string normalization function. Replace as needed.
+		const normalize = (str: string) => str.normalize().toLowerCase();
+		const normalizedInput = normalize(inputValue);
+		return (
+			normalizedInput === '' ||
+			normalize(item.title).includes(normalizedInput) ||
+			normalize(item.author).includes(normalizedInput)
+		);
+	};
+
+	const { elements, states, helpers } = createCombobox({
+		filterFunction,
+		items: books,
+		itemToString: (item) => item.title,
+	});
+	const { menu, input, item } = elements;
+	const { open, inputValue, filteredItems } = states;
+	const { isSelected } = helpers;
 </script>
 
 <label class="cursor-pointer">
diff --git a/src/docs/previews/context-menu/main/css.svelte b/src/docs/previews/context-menu/main/css.svelte
index 3fd421cc..e0b55ec9 100644
--- a/src/docs/previews/context-menu/main/css.svelte
+++ b/src/docs/previews/context-menu/main/css.svelte
@@ -5,18 +5,18 @@
 	import Check from '~icons/lucide/check';
 
 	const {
-		trigger,
-		menu,
-		item,
-		checkboxItem,
-		separator,
-		createSubMenu,
-		createMenuRadioGroup,
+		elements: { trigger, menu, item, checkboxItem, separator },
+		builders: { createSubmenu, createMenuRadioGroup },
 	} = createContextMenu();
 
-	const { subMenu: subMenuA, subTrigger: subTriggerA } = createSubMenu();
+	const {
+		elements: { subMenu: subMenuA, subTrigger: subTriggerA },
+	} = createSubmenu();
 
-	const { radioGroup, radioItem, isChecked } = createMenuRadioGroup({
+	const {
+		elements: { radioGroup, radioItem },
+		helpers: { isChecked },
+	} = createMenuRadioGroup({
 		value: 'Hunter Johnston',
 	});
 
diff --git a/src/docs/previews/context-menu/main/tailwind.svelte b/src/docs/previews/context-menu/main/tailwind.svelte
index 8d1d6fda..cddb5a2b 100644
--- a/src/docs/previews/context-menu/main/tailwind.svelte
+++ b/src/docs/previews/context-menu/main/tailwind.svelte
@@ -1,22 +1,21 @@
 <script lang="ts">
 	import { createContextMenu } from '$lib';
 	import { writable } from 'svelte/store';
-	import ChevronRight from '~icons/lucide/chevron-right';
-	import Check from '~icons/lucide/check';
+	import { ChevronRight, Check } from 'lucide-svelte';
 
 	const {
-		trigger,
-		menu,
-		item,
-		checkboxItem,
-		separator,
-		createSubMenu,
-		createMenuRadioGroup,
+		elements: { trigger, menu, item, checkboxItem, separator },
+		builders: { createSubmenu, createMenuRadioGroup },
 	} = createContextMenu();
 
-	const { subMenu: subMenuA, subTrigger: subTriggerA } = createSubMenu();
+	const {
+		elements: { subMenu: subMenuA, subTrigger: subTriggerA },
+	} = createSubmenu();
 
-	const { radioGroup, radioItem, isChecked } = createMenuRadioGroup({
+	const {
+		elements: { radioGroup, radioItem },
+		helpers: { isChecked },
+	} = createMenuRadioGroup({
 		value: 'Hunter Johnston',
 	});
 
diff --git a/src/docs/previews/dialog/alert/css.svelte b/src/docs/previews/dialog/alert/css.svelte
index dc540d3f..7e2bbf4c 100644
--- a/src/docs/previews/dialog/alert/css.svelte
+++ b/src/docs/previews/dialog/alert/css.svelte
@@ -4,10 +4,11 @@
 	import { flyAndScale } from '$docs/utils';
 	import X from '~icons/lucide/x';
 
-	const { trigger, portal, overlay, content, title, description, close, open } =
-		createDialog({
-			role: 'alertdialog',
-		});
+	const {
+		elements: { trigger, overlay, content, title, description, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 </script>
 
 <button melt={$trigger} class="trigger"> Delete Item </button>
diff --git a/src/docs/previews/dialog/alert/tailwind.svelte b/src/docs/previews/dialog/alert/tailwind.svelte
index c2724d66..b8c3ed58 100644
--- a/src/docs/previews/dialog/alert/tailwind.svelte
+++ b/src/docs/previews/dialog/alert/tailwind.svelte
@@ -2,12 +2,13 @@
 	import { createDialog } from '@melt-ui/svelte';
 	/** Internal helpers */
 	import { flyAndScale } from '$docs/utils';
-	import X from '~icons/lucide/x';
+	import { X } from 'lucide-svelte';
 
-	const { trigger, portal, overlay, content, title, description, close, open } =
-		createDialog({
-			role: 'alertdialog',
-		});
+	const {
+		elements: { trigger, overlay, content, title, description, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 </script>
 
 <button
diff --git a/src/docs/previews/dialog/drawer/css.svelte b/src/docs/previews/dialog/drawer/css.svelte
index bcf012bd..f6108f46 100644
--- a/src/docs/previews/dialog/drawer/css.svelte
+++ b/src/docs/previews/dialog/drawer/css.svelte
@@ -4,8 +4,11 @@
 	// Internal helpers
 	import X from '~icons/lucide/x';
 
-	const { trigger, portal, overlay, content, title, description, close, open } =
-		createDialog();
+	const {
+		elements: { trigger, overlay, content, title, description, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 </script>
 
 <button melt={$trigger} class="trigger"> View Notifications </button>
diff --git a/src/docs/previews/dialog/drawer/tailwind.svelte b/src/docs/previews/dialog/drawer/tailwind.svelte
index c398d75c..26857f2a 100644
--- a/src/docs/previews/dialog/drawer/tailwind.svelte
+++ b/src/docs/previews/dialog/drawer/tailwind.svelte
@@ -2,10 +2,13 @@
 	import { createDialog } from '$lib/builders/dialog';
 	import { fade, fly } from 'svelte/transition';
 	// Internal helpers
-	import X from '~icons/lucide/x';
+	import { X } from 'lucide-svelte';
 
-	const { trigger, portal, overlay, content, title, description, close, open } =
-		createDialog();
+	const {
+		elements: { trigger, overlay, content, title, description, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 </script>
 
 <button
diff --git a/src/docs/previews/dialog/main/css.svelte b/src/docs/previews/dialog/main/css.svelte
index 59f31304..1b427b90 100644
--- a/src/docs/previews/dialog/main/css.svelte
+++ b/src/docs/previews/dialog/main/css.svelte
@@ -4,8 +4,11 @@
 	import { flyAndScale } from '$docs/utils';
 	import X from '~icons/lucide/x';
 
-	const { trigger, portal, overlay, content, title, description, close, open } =
-		createDialog();
+	const {
+		elements: { trigger, overlay, content, title, description, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 </script>
 
 <button melt={$trigger} class="trigger"> Open Dialog </button>
diff --git a/src/docs/previews/dialog/main/tailwind.svelte b/src/docs/previews/dialog/main/tailwind.svelte
index 4b46f2aa..6b25d62d 100644
--- a/src/docs/previews/dialog/main/tailwind.svelte
+++ b/src/docs/previews/dialog/main/tailwind.svelte
@@ -2,10 +2,13 @@
 	import { createDialog } from '@melt-ui/svelte';
 	/** Internal helpers */
 	import { flyAndScale } from '$docs/utils';
-	import X from '~icons/lucide/x';
+	import { X } from 'lucide-svelte';
 
-	const { trigger, portal, overlay, content, title, description, close, open } =
-		createDialog();
+	const {
+		elements: { trigger, overlay, content, title, description, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 </script>
 
 <button
diff --git a/src/docs/previews/dialog/nested/tailwind.svelte b/src/docs/previews/dialog/nested/tailwind.svelte
index 22d49745..b5d38edf 100644
--- a/src/docs/previews/dialog/nested/tailwind.svelte
+++ b/src/docs/previews/dialog/nested/tailwind.svelte
@@ -4,18 +4,23 @@
 	import { flyAndScale } from '$docs/utils';
 	import X from '~icons/lucide/x';
 
-	const { trigger, portal, overlay, content, title, description, close, open } =
-		createDialog();
+	const {
+		elements: { trigger, overlay, content, title, description, close },
+		actions: { portal },
+		states: { open },
+	} = createDialog();
 
 	const {
-		trigger: triggerNested,
-		portal: portalNested,
-		overlay: overlayNested,
-		content: contentNested,
-		title: titleNested,
-		description: descriptionNested,
-		close: closeNested,
-		open: openNested,
+		elements: {
+			trigger: triggerNested,
+			overlay: overlayNested,
+			content: contentNested,
+			title: titleNested,
+			description: descriptionNested,
+			close: closeNested,
+		},
+		actions: { portal: portalNested },
+		states: { open: openNested },
 	} = createDialog();
 </script>
 
diff --git a/src/docs/previews/dropdown-menu/main/tailwind.svelte b/src/docs/previews/dropdown-menu/main/tailwind.svelte
index 7cca7b49..bd44f5bd 100644
--- a/src/docs/previews/dropdown-menu/main/tailwind.svelte
+++ b/src/docs/previews/dropdown-menu/main/tailwind.svelte
@@ -1,24 +1,21 @@
 <script lang="ts">
 	import { createDropdownMenu } from '$lib';
 	import { writable } from 'svelte/store';
-	import AlignJustify from '~icons/lucide/align-justify';
-	import ChevronRight from '~icons/lucide/chevron-right';
-	import Check from '~icons/lucide/check';
+	import { AlignJustify, ChevronRight, Check } from 'lucide-svelte';
 
 	const {
-		trigger,
-		menu,
-		item,
-		checkboxItem,
-		arrow,
-		separator,
-		createSubMenu,
-		createMenuRadioGroup,
+		elements: { trigger, menu, item, checkboxItem, separator, arrow },
+		builders: { createSubmenu, createMenuRadioGroup },
 	} = createDropdownMenu();
 
-	const { subMenu: subMenuA, subTrigger: subTriggerA } = createSubMenu();
+	const {
+		elements: { subMenu: subMenuA, subTrigger: subTriggerA },
+	} = createSubmenu();
 
-	const { radioGroup, radioItem, isChecked } = createMenuRadioGroup({
+	const {
+		elements: { radioGroup, radioItem },
+		helpers: { isChecked },
+	} = createMenuRadioGroup({
 		value: 'Hunter Johnston',
 	});
 
diff --git a/src/docs/previews/hover-card/main/tailwind.svelte b/src/docs/previews/hover-card/main/tailwind.svelte
index c2194564..3ca76796 100644
--- a/src/docs/previews/hover-card/main/tailwind.svelte
+++ b/src/docs/previews/hover-card/main/tailwind.svelte
@@ -2,7 +2,10 @@
 	import { createHoverCard } from '@melt-ui/svelte';
 	import { fade } from 'svelte/transition';
 
-	const { trigger, content, open, arrow } = createHoverCard();
+	const {
+		elements: { trigger, content, arrow },
+		states: { open },
+	} = createHoverCard();
 </script>
 
 <a
diff --git a/src/docs/previews/label/main/tailwind.svelte b/src/docs/previews/label/main/tailwind.svelte
index e4808d4e..137f52c2 100644
--- a/src/docs/previews/label/main/tailwind.svelte
+++ b/src/docs/previews/label/main/tailwind.svelte
@@ -1,7 +1,9 @@
 <script lang="ts">
 	import { createLabel } from '@melt-ui/svelte';
 
-	const root = createLabel();
+	const {
+		elements: { root },
+	} = createLabel();
 </script>
 
 <form>
diff --git a/src/docs/previews/menubar/main/tailwind.svelte b/src/docs/previews/menubar/main/tailwind.svelte
index a4e14544..6019ec66 100644
--- a/src/docs/previews/menubar/main/tailwind.svelte
+++ b/src/docs/previews/menubar/main/tailwind.svelte
@@ -1,47 +1,61 @@
 <script lang="ts">
 	import { createMenubar } from '@melt-ui/svelte';
 	import { writable } from 'svelte/store';
-	import ChevronRight from '~icons/lucide/chevron-right';
-	import Check from '~icons/lucide/check';
+	import { ChevronRight, Check } from 'lucide-svelte';
 
-	const { menubar, createMenu } = createMenubar();
+	const {
+		elements: { menubar },
+		builders: { createMenu },
+	} = createMenubar();
 
 	const {
-		trigger,
-		menu,
-		item,
-		separator,
-		createSubmenu,
-		createMenuRadioGroup,
+		elements: { trigger, menu, item, separator },
+		builders: { createSubmenu, createMenuRadioGroup },
 	} = createMenu();
-	const { subMenu, subTrigger } = createSubmenu();
-	const { radioGroup, radioItem, isChecked } = createMenuRadioGroup({
+
+	const {
+		elements: { subMenu, subTrigger },
+	} = createSubmenu();
+
+	const {
+		elements: { radioGroup, radioItem },
+		helpers: { isChecked },
+	} = createMenuRadioGroup({
 		value: 'Nord',
 	});
 
 	const {
-		trigger: triggerA,
-		menu: menuA,
-		item: itemA,
-		separator: separatorA,
+		elements: {
+			trigger: triggerA,
+			menu: menuA,
+			item: itemA,
+			separator: separatorA,
+		},
 	} = createMenu();
 
 	const {
-		trigger: triggerB,
-		menu: menuB,
-		item: itemB,
-		checkboxItem: checkboxItemB,
-		separator: separatorB,
-		createSubmenu: createSubmenuB,
+		elements: {
+			trigger: triggerB,
+			menu: menuB,
+			item: itemB,
+			checkboxItem: checkboxItemB,
+			separator: separatorB,
+		},
+		builders: { createSubmenu: createSubmenuB },
 	} = createMenu();
-	const { subMenu: subMenuB, subTrigger: subTriggerB } = createSubmenuB();
 
 	const {
-		trigger: triggerC,
-		menu: menuC,
-		item: itemC,
-		checkboxItem: checkboxItemC,
-		separator: separatorC,
+		elements: { subMenu: subMenuB, subTrigger: subTriggerB },
+	} = createSubmenuB();
+
+	const {
+		elements: {
+			trigger: triggerC,
+			menu: menuC,
+			item: itemC,
+			checkboxItem: checkboxItemC,
+			separator: separatorC,
+		},
 	} = createMenu();
 
 	const themesArr = ['Nord', 'GitHub Dark', 'Moonlight'];
diff --git a/src/docs/previews/pagination/main/tailwind.svelte b/src/docs/previews/pagination/main/tailwind.svelte
index 48d0461f..a2568e1e 100644
--- a/src/docs/previews/pagination/main/tailwind.svelte
+++ b/src/docs/previews/pagination/main/tailwind.svelte
@@ -1,15 +1,16 @@
 <script lang="ts">
 	import { createPagination } from '@melt-ui/svelte';
-	import ChevronLeft from '~icons/lucide/chevron-left';
-	import ChevronRight from '~icons/lucide/chevron-right';
+	import { ChevronLeft, ChevronRight } from 'lucide-svelte';
 
-	const { prevButton, nextButton, pages, pageTrigger, range, root } =
-		createPagination({
-			count: 100,
-			perPage: 10,
-			page: 1,
-			siblingCount: 1,
-		});
+	const {
+		elements: { root, pageTrigger, prevButton, nextButton },
+		states: { pages, range },
+	} = createPagination({
+		count: 100,
+		perPage: 10,
+		page: 1,
+		siblingCount: 1,
+	});
 </script>
 
 <nav
diff --git a/src/docs/previews/pin-input/main/tailwind.svelte b/src/docs/previews/pin-input/main/tailwind.svelte
index 4cb020fa..2a0eaaf1 100644
--- a/src/docs/previews/pin-input/main/tailwind.svelte
+++ b/src/docs/previews/pin-input/main/tailwind.svelte
@@ -1,7 +1,9 @@
 <script lang="ts">
 	import { createPinInput } from '@melt-ui/svelte';
 
-	const { root, input } = createPinInput();
+	const {
+		elements: { root, input },
+	} = createPinInput();
 </script>
 
 <div melt={$root} class="flex items-center gap-2">
diff --git a/src/docs/previews/popover/main/tailwind.svelte b/src/docs/previews/popover/main/tailwind.svelte
index 9c4b7f55..4498318f 100644
--- a/src/docs/previews/popover/main/tailwind.svelte
+++ b/src/docs/previews/popover/main/tailwind.svelte
@@ -1,10 +1,12 @@
 <script lang="ts">
 	import { createPopover } from '@melt-ui/svelte';
 	import { fade } from 'svelte/transition';
-	import Settings2 from '~icons/lucide/settings2';
-	import X from '~icons/lucide/x';
+	import { Settings2, X } from 'lucide-svelte';
 
-	const { trigger, content, open, arrow, close } = createPopover();
+	const {
+		elements: { trigger, content, arrow, close },
+		states: { open },
+	} = createPopover();
 </script>
 
 <button
diff --git a/src/docs/previews/progress/main/tailwind.svelte b/src/docs/previews/progress/main/tailwind.svelte
index 943a6e03..8b6e837e 100644
--- a/src/docs/previews/progress/main/tailwind.svelte
+++ b/src/docs/previews/progress/main/tailwind.svelte
@@ -1,7 +1,11 @@
 <script lang="ts">
 	import { createProgress } from '@melt-ui/svelte';
 
-	const { progress, value, max } = createProgress({
+	const {
+		elements: { root },
+		states: { value },
+		options: { max },
+	} = createProgress({
 		value: 30,
 		max: 100,
 	});
@@ -14,7 +18,7 @@
 </script>
 
 <div
-	melt={$progress}
+	melt={$root}
 	class="relative h-6 w-[300px] overflow-hidden rounded-[99999px] bg-black/40"
 >
 	<div
diff --git a/src/docs/previews/radio-group/main/tailwind.svelte b/src/docs/previews/radio-group/main/tailwind.svelte
index 1da0251c..e8ebd9bf 100644
--- a/src/docs/previews/radio-group/main/tailwind.svelte
+++ b/src/docs/previews/radio-group/main/tailwind.svelte
@@ -1,7 +1,10 @@
 <script lang="ts">
 	import { createRadioGroup } from '@melt-ui/svelte';
 
-	const { root, item, isChecked } = createRadioGroup({
+	const {
+		elements: { root, item },
+		helpers: { isChecked },
+	} = createRadioGroup({
 		value: 'default',
 	});
 
diff --git a/src/docs/previews/select/main/tailwind.svelte b/src/docs/previews/select/main/tailwind.svelte
index 62d45254..c48af211 100644
--- a/src/docs/previews/select/main/tailwind.svelte
+++ b/src/docs/previews/select/main/tailwind.svelte
@@ -1,10 +1,12 @@
 <script lang="ts">
 	import { createSelect } from '@melt-ui/svelte';
-	import Check from '~icons/lucide/check';
-	import ChevronDown from '~icons/lucide/chevron-down';
+	import { Check, ChevronDown } from 'lucide-svelte';
 
-	const { label, trigger, menu, option, isSelected, group, groupLabel } =
-		createSelect();
+	const {
+		elements: { trigger, menu, option, group, groupLabel },
+		states: { label },
+		helpers: { isSelected },
+	} = createSelect();
 
 	const options = {
 		sweet: ['Caramel', 'Chocolate', 'Strawberry', 'Cookies & Cream'],
diff --git a/src/docs/previews/separator/main/css.svelte b/src/docs/previews/separator/main/css.svelte
index b69891d0..acafe1dd 100644
--- a/src/docs/previews/separator/main/css.svelte
+++ b/src/docs/previews/separator/main/css.svelte
@@ -3,11 +3,15 @@
 
 	export let orientation: CreateSeparatorProps['orientation'] = 'vertical';
 
-	const { root: vertical } = createSeparator({
+	const {
+		elements: { root: vertical },
+	} = createSeparator({
 		orientation,
 	});
 
-	const { root: horizontalSeparator } = createSeparator({
+	const {
+		elements: { root: horizontal },
+	} = createSeparator({
 		orientation: 'horizontal',
 		decorative: true,
 	});
@@ -17,7 +21,7 @@
 
 <h2>Melt UI</h2>
 <p>Flavors for everyone</p>
-<div melt={$horizontalSeparator} class="separator separator--horizontal" />
+<div melt={$horizontal} class="separator separator--horizontal" />
 <div class="ice-creams">
 	{#each icecreams as icecream, i}
 		<p>{icecream}</p>
diff --git a/src/docs/previews/separator/main/tailwind.svelte b/src/docs/previews/separator/main/tailwind.svelte
index 6a6a46c7..b3a6c6ee 100644
--- a/src/docs/previews/separator/main/tailwind.svelte
+++ b/src/docs/previews/separator/main/tailwind.svelte
@@ -3,11 +3,15 @@
 
 	export let orientation: CreateSeparatorProps['orientation'] = 'vertical';
 
-	const { root: vertical } = createSeparator({
+	const {
+		elements: { root: vertical },
+	} = createSeparator({
 		orientation,
 	});
 
-	const { root: horizontalSeparator } = createSeparator({
+	const {
+		elements: { root: horizontal },
+	} = createSeparator({
 		orientation: 'horizontal',
 		decorative: true,
 	});
@@ -18,7 +22,7 @@
 <div>
 	<h2 class="font-bold">Melt UI</h2>
 	<p>Flavors for everyone</p>
-	<div melt={$horizontalSeparator} class="my-3.5 h-[1px] w-full bg-white" />
+	<div melt={$horizontal} class="my-3.5 h-[1px] w-full bg-white" />
 	<div class="flex items-center space-x-3.5">
 		{#each icecreams as icecream, i}
 			<p>{icecream}</p>
diff --git a/src/docs/previews/slider/main/tailwind.svelte b/src/docs/previews/slider/main/tailwind.svelte
index 8ce6d236..72d7caae 100644
--- a/src/docs/previews/slider/main/tailwind.svelte
+++ b/src/docs/previews/slider/main/tailwind.svelte
@@ -1,13 +1,15 @@
 <script lang="ts">
 	import { createSlider } from '@melt-ui/svelte';
 
-	const { slider, range, thumb } = createSlider({
+	const {
+		elements: { root, range, thumb },
+	} = createSlider({
 		value: [30],
 		max: 100,
 	});
 </script>
 
-<span melt={$slider} class="relative flex h-[20px] w-[200px] items-center">
+<span melt={$root} class="relative flex h-[20px] w-[200px] items-center">
 	<span class="block h-[3px] w-full bg-black/40">
 		<span melt={$range} class="h-[3px] bg-white" />
 	</span>
diff --git a/src/docs/previews/slider/range/tailwind.svelte b/src/docs/previews/slider/range/tailwind.svelte
index 6fbc6544..7fc84a20 100644
--- a/src/docs/previews/slider/range/tailwind.svelte
+++ b/src/docs/previews/slider/range/tailwind.svelte
@@ -1,21 +1,23 @@
 <script lang="ts">
 	import { createSlider } from '@melt-ui/svelte';
 
-	const { slider, range, thumb, value } = createSlider({
+	const {
+		elements: { root, range, thumb },
+		states: { value },
+	} = createSlider({
 		value: [20, 80],
 		max: 100,
 	});
 </script>
 
-<span {...$slider} class="relative flex h-[20px] w-[200px] items-center">
+<span melt={$root} class="relative flex h-[20px] w-[200px] items-center">
 	<span class="block h-[3px] w-full bg-black/40">
-		<span {...$range} class="h-[3px] bg-white" />
+		<span melt={$range} class="h-[3px] bg-white" />
 	</span>
 
 	{#each { length: $value.length } as _}
 		<span
-			{...$thumb()}
-			use:thumb
+			melt={$thumb()}
 			class="block h-5 w-5 rounded-full bg-white focus:ring-4 focus:ring-black/40"
 		/>
 	{/each}
diff --git a/src/docs/previews/slider/vertical/tailwind.svelte b/src/docs/previews/slider/vertical/tailwind.svelte
index 42d23fb6..dbb43048 100644
--- a/src/docs/previews/slider/vertical/tailwind.svelte
+++ b/src/docs/previews/slider/vertical/tailwind.svelte
@@ -1,7 +1,9 @@
 <script lang="ts">
 	import { createSlider } from '@melt-ui/svelte';
 
-	const { slider, range, thumb } = createSlider({
+	const {
+		elements: { root, range, thumb },
+	} = createSlider({
 		value: [30],
 		max: 100,
 		orientation: 'vertical',
@@ -9,7 +11,7 @@
 </script>
 
 <span
-	melt={$slider}
+	melt={$root}
 	class="relative flex h-[200px] w-[3px] flex-col items-center"
 >
 	<span class="block h-[200px] w-full bg-black/40">
diff --git a/src/docs/previews/switch/main/tailwind.svelte b/src/docs/previews/switch/main/tailwind.svelte
index e3ec309b..4560599b 100644
--- a/src/docs/previews/switch/main/tailwind.svelte
+++ b/src/docs/previews/switch/main/tailwind.svelte
@@ -1,7 +1,10 @@
 <script lang="ts">
 	import { createSwitch } from '@melt-ui/svelte';
 
-	const { root, input, isChecked } = createSwitch();
+	const {
+		elements: { root, input },
+		helpers: { isChecked },
+	} = createSwitch();
 </script>
 
 <form>
diff --git a/src/docs/previews/tabs/main/tailwind.svelte b/src/docs/previews/tabs/main/tailwind.svelte
index 7cb01b05..3a3244d4 100644
--- a/src/docs/previews/tabs/main/tailwind.svelte
+++ b/src/docs/previews/tabs/main/tailwind.svelte
@@ -1,7 +1,9 @@
 <script lang="ts">
 	import { createTabs } from '@melt-ui/svelte';
 
-	const { root, list, content, trigger } = createTabs({
+	const {
+		elements: { root, list, content, trigger },
+	} = createTabs({
 		value: 'tab1',
 	});
 </script>
diff --git a/src/docs/previews/tags-input/main/tailwind.svelte b/src/docs/previews/tags-input/main/tailwind.svelte
index e0893cc2..d3ff876b 100644
--- a/src/docs/previews/tags-input/main/tailwind.svelte
+++ b/src/docs/previews/tags-input/main/tailwind.svelte
@@ -1,8 +1,11 @@
 <script lang="ts">
 	import { createTagsInput } from '@melt-ui/svelte';
-	import X from '~icons/lucide/x';
+	import { X } from 'lucide-svelte';
 
-	const { root, input, tags, tag, deleteTrigger, edit } = createTagsInput({
+	const {
+		elements: { root, input, tag, deleteTrigger, edit },
+		states: { tags },
+	} = createTagsInput({
 		tags: ['Svelte', 'Typescript'],
 		unique: true,
 	});
diff --git a/src/docs/previews/toast/main/tailwind.svelte b/src/docs/previews/toast/main/tailwind.svelte
index 0f7835f9..e0e3749b 100644
--- a/src/docs/previews/toast/main/tailwind.svelte
+++ b/src/docs/previews/toast/main/tailwind.svelte
@@ -10,8 +10,11 @@
 		color: string;
 	};
 
-	const { toasts, addToast, content, title, description, close } =
-		createToasts<ToastData>({});
+	const {
+		elements: { content, title, description, close },
+		helpers: { addToast },
+		states: { toasts },
+	} = createToasts<ToastData>();
 
 	const toastData: ToastData[] = [
 		{
diff --git a/src/docs/previews/toggle-group/main/tailwind.svelte b/src/docs/previews/toggle-group/main/tailwind.svelte
index 3e5972a7..b60c6c2b 100644
--- a/src/docs/previews/toggle-group/main/tailwind.svelte
+++ b/src/docs/previews/toggle-group/main/tailwind.svelte
@@ -1,10 +1,10 @@
 <script lang="ts">
 	import { createToggleGroup } from '@melt-ui/svelte';
-	import AlignCenter from '~icons/lucide/align-center';
-	import AlignLeft from '~icons/lucide/align-left';
-	import AlignRight from '~icons/lucide/align-right';
+	import { AlignCenter, AlignLeft, AlignRight } from 'lucide-svelte';
 
-	const { root, item } = createToggleGroup();
+	const {
+		elements: { root, item },
+	} = createToggleGroup();
 </script>
 
 <div
diff --git a/src/docs/previews/toggle/main/tailwind.svelte b/src/docs/previews/toggle/main/tailwind.svelte
index cf6032d1..b6fb828d 100644
--- a/src/docs/previews/toggle/main/tailwind.svelte
+++ b/src/docs/previews/toggle/main/tailwind.svelte
@@ -1,12 +1,14 @@
 <script lang="ts">
 	import { createToggle } from '@melt-ui/svelte';
-	import Italic from '~icons/lucide/italic';
+	import { Italic } from 'lucide-svelte';
 
-	const { toggle } = createToggle();
+	const {
+		elements: { root },
+	} = createToggle();
 </script>
 
 <button
-	melt={$toggle}
+	melt={$root}
 	aria-label="Toggle italic"
 	class="grid h-9 w-9 place-items-center items-center justify-center rounded
 	bg-white text-base leading-4 text-magnum-800 shadow-lg hover:bg-magnum-100
diff --git a/src/docs/previews/toolbar/main/tailwind.svelte b/src/docs/previews/toolbar/main/tailwind.svelte
index 4a53dced..61ef82f8 100644
--- a/src/docs/previews/toolbar/main/tailwind.svelte
+++ b/src/docs/previews/toolbar/main/tailwind.svelte
@@ -1,18 +1,28 @@
 <script lang="ts">
 	import { createToolbar } from '@melt-ui/svelte';
 
-	import Bold from '~icons/lucide/bold';
-	import Italic from '~icons/lucide/italic';
-	import Strikethrough from '~icons/lucide/strikethrough';
-	import AlignLeft from '~icons/lucide/align-left';
-	import AlignCenter from '~icons/lucide/align-center';
-	import AlignRight from '~icons/lucide/align-right';
+	// Icons
+	import {
+		Bold,
+		Italic,
+		Strikethrough,
+		AlignLeft,
+		AlignCenter,
+		AlignRight,
+	} from 'lucide-svelte';
 
-	const { root, button, link, separator, createToolbarGroup } = createToolbar();
-	const { root: fontGroup, item: fontItem } = createToolbarGroup({
+	const {
+		elements: { root, button, link, separator },
+		builders: { createToolbarGroup },
+	} = createToolbar();
+	const {
+		elements: { root: fontGroup, item: fontItem },
+	} = createToolbarGroup({
 		type: 'multiple',
 	});
-	const { root: alignGroup, item: alignItem } = createToolbarGroup();
+	const {
+		elements: { root: alignGroup, item: alignItem },
+	} = createToolbarGroup();
 </script>
 
 <div
@@ -43,11 +53,7 @@
 		</button>
 	</div>
 	<div class="separator" melt={$separator} />
-	<a
-		href="https://www.melt-ui.com/"
-		class="link nowrap flex-shrink-0"
-		melt={$link}
-	>
+	<a href="/" class="link nowrap flex-shrink-0" melt={$link}>
 		Edited 2 hours ago
 	</a>
 	<button class="button" melt={$button}>Save</button>
diff --git a/src/docs/previews/tooltip/main/tailwind.svelte b/src/docs/previews/tooltip/main/tailwind.svelte
index 11ce5778..81ad3256 100644
--- a/src/docs/previews/tooltip/main/tailwind.svelte
+++ b/src/docs/previews/tooltip/main/tailwind.svelte
@@ -1,9 +1,12 @@
 <script lang="ts">
 	import { createTooltip } from '@melt-ui/svelte';
 	import { fade } from 'svelte/transition';
-	import Plus from '~icons/lucide/plus';
+	import { Plus } from 'lucide-svelte';
 
-	const { trigger, content, open, arrow } = createTooltip({
+	const {
+		elements: { trigger, content, arrow },
+		states: { open },
+	} = createTooltip({
 		positioning: {
 			placement: 'top',
 		},
diff --git a/src/lib/builders/accordion/create.ts b/src/lib/builders/accordion/create.ts
index 445e98aa..c80d18d0 100644
--- a/src/lib/builders/accordion/create.ts
+++ b/src/lib/builders/accordion/create.ts
@@ -6,6 +6,8 @@ import {
 	generateId,
 	getElementByMeltId,
 	kbd,
+	omit,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
 import { tick } from 'svelte';
@@ -22,15 +24,18 @@ const { name, selector } = createElHelpers<AccordionParts>('accordion');
 
 const defaults = {
 	type: 'single',
+	value: undefined,
+	disabled: undefined,
 } satisfies Defaults<CreateAccordionProps>;
 
 export const createAccordion = <T extends AccordionType = 'single'>(
 	props?: CreateAccordionProps<T>
 ) => {
-	const withDefaults = { ...defaults, ...props } as CreateAccordionProps<T>;
-	const options = writable({
-		disabled: withDefaults.disabled,
-	});
+	const withDefaults = { ...defaults, ...props };
+
+	const options = toWritableStores(omit(withDefaults, 'value'));
+
+	const { disabled } = options;
 
 	const value = writable<string | string[] | undefined>(withDefaults.value);
 
@@ -85,14 +90,14 @@ export const createAccordion = <T extends AccordionType = 'single'>(
 	});
 
 	const trigger = builder(name('trigger'), {
-		stores: [value, options],
-		returned: ([$value, $options]) => {
+		stores: [value, disabled],
+		returned: ([$value, $disabled]) => {
 			return (props: AccordionItemProps) => {
 				const { value: itemValue, disabled } = parseItemProps(props);
 				// generate the content ID here so that we can grab it in the content
 				// builder action to ensure the values match.
 				return {
-					disabled: $options.disabled || disabled,
+					disabled: $disabled || disabled,
 					'aria-expanded': isSelected(itemValue, $value) ? true : false,
 					'aria-disabled': disabled ? true : false,
 					'data-disabled': disabled ? true : undefined,
@@ -159,14 +164,14 @@ export const createAccordion = <T extends AccordionType = 'single'>(
 	});
 
 	const content = builder(name('content'), {
-		stores: [value, options],
-		returned: ([$value, $options]) => {
+		stores: [value, disabled],
+		returned: ([$value, $disabled]) => {
 			return (props: AccordionItemProps) => {
 				const { value: itemValue } = parseItemProps(props);
 				const selected = isSelected(itemValue, $value);
 				return {
 					'data-state': selected ? 'open' : 'closed',
-					'data-disabled': $options.disabled ? true : undefined,
+					'data-disabled': $disabled ? true : undefined,
 					'data-value': itemValue,
 					hidden: selected ? undefined : true,
 				};
@@ -203,14 +208,19 @@ export const createAccordion = <T extends AccordionType = 'single'>(
 	});
 
 	return {
-		root,
-		/** Initial value of accordion */
-		value: value as Writable<CreateAccordionProps<T>['value']>,
-		item,
-		trigger,
-		content,
-		isSelected: isSelectedStore,
+		elements: {
+			root,
+			item,
+			trigger,
+			content,
+			heading,
+		},
+		states: {
+			value: value as Writable<CreateAccordionProps<T>['value']>,
+		},
+		helpers: {
+			isSelected: isSelectedStore,
+		},
 		options,
-		heading,
 	};
 };
diff --git a/src/lib/builders/accordion/types.ts b/src/lib/builders/accordion/types.ts
index e1adeb80..db5798e9 100644
--- a/src/lib/builders/accordion/types.ts
+++ b/src/lib/builders/accordion/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createAccordion } from './create';
 
 export type AccordionType = 'single' | 'multiple';
@@ -21,4 +22,8 @@ export type AccordionHeadingProps =
 	  }
 	| number;
 
-export type CreateAccordionReturn = ReturnType<typeof createAccordion>;
+export type Accordion = BuilderReturn<typeof createAccordion>;
+export type AccordionElements = Accordion['elements'];
+export type AccordionOptions = Accordion['options'];
+export type AccordionStates = Accordion['states'];
+export type AccordionHelpers = Accordion['helpers'];
diff --git a/src/lib/builders/avatar/create.ts b/src/lib/builders/avatar/create.ts
index c408eae2..30099269 100644
--- a/src/lib/builders/avatar/create.ts
+++ b/src/lib/builders/avatar/create.ts
@@ -1,20 +1,21 @@
-import { builder, effect, isBrowser, styleToString } from '$lib/internal/helpers';
+import { builder, effect, isBrowser, styleToString, toWritableStores } from '$lib/internal/helpers';
 import { writable } from 'svelte/store';
 import type { CreateAvatarProps, ImageLoadingStatus } from './types';
 
 const defaults = {
 	src: '',
+	delayMs: 0,
 } satisfies CreateAvatarProps;
 
-export const createAvatar = (props: CreateAvatarProps = defaults) => {
-	const withDefaults = { ...defaults, ...props };
-	const { delayMs } = withDefaults;
+export const createAvatar = (props?: CreateAvatarProps) => {
+	const withDefaults = { ...defaults, ...props } satisfies CreateAvatarProps;
 
-	const src = writable(withDefaults.src);
+	const options = toWritableStores(withDefaults);
+	const { src, delayMs } = options;
 
 	const loadingStatus = writable<ImageLoadingStatus>('loading');
 
-	effect([src], ([$src]) => {
+	effect([src, delayMs], ([$src, $delayMs]) => {
 		if (isBrowser) {
 			const image = new Image();
 			image.src = $src;
@@ -22,7 +23,7 @@ export const createAvatar = (props: CreateAvatarProps = defaults) => {
 				if (delayMs !== undefined) {
 					const timerId = window.setTimeout(() => {
 						loadingStatus.set('loaded');
-					}, delayMs);
+					}, $delayMs);
 					return () => window.clearTimeout(timerId);
 				} else {
 					loadingStatus.set('loaded');
@@ -60,7 +61,13 @@ export const createAvatar = (props: CreateAvatarProps = defaults) => {
 	});
 
 	return {
-		image,
-		fallback,
+		elements: {
+			image,
+			fallback,
+		},
+		states: {
+			loadingStatus,
+		},
+		options,
 	};
 };
diff --git a/src/lib/builders/avatar/types.ts b/src/lib/builders/avatar/types.ts
index 22667625..1783d148 100644
--- a/src/lib/builders/avatar/types.ts
+++ b/src/lib/builders/avatar/types.ts
@@ -1,4 +1,5 @@
 import type { createAvatar } from './create';
+import type { BuilderReturn } from '$lib/internal/types';
 
 export type ImageLoadingStatus = 'loading' | 'loaded' | 'error';
 
@@ -7,4 +8,7 @@ export type CreateAvatarProps = {
 	delayMs?: number;
 };
 
-export type CreateAvatarReturn = ReturnType<typeof createAvatar>;
+export type Avatar = BuilderReturn<typeof createAvatar>;
+export type AvatarElements = Avatar['elements'];
+export type AvatarOptions = Avatar['options'];
+export type AvatarStates = Avatar['states'];
diff --git a/src/lib/builders/checkbox/create.ts b/src/lib/builders/checkbox/create.ts
index 41a45809..8a9cf150 100644
--- a/src/lib/builders/checkbox/create.ts
+++ b/src/lib/builders/checkbox/create.ts
@@ -4,38 +4,42 @@ import {
 	executeCallbacks,
 	kbd,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
 import { derived, get, writable } from 'svelte/store';
 import type { CreateCheckboxProps } from './types';
+import { omit } from '../../internal/helpers/object';
 
 const defaults = {
 	checked: false,
 	disabled: false,
 	required: false,
+	name: undefined,
+	value: undefined,
 } satisfies Defaults<CreateCheckboxProps>;
 
-export function createCheckbox(props: CreateCheckboxProps = {}) {
-	const propsWithDefaults = { ...defaults, ...props };
-	const options = writable({
-		disabled: propsWithDefaults.disabled,
-		required: propsWithDefaults.required,
-		name: propsWithDefaults.name,
-		value: propsWithDefaults.value,
-	});
-	const checked = writable(propsWithDefaults.checked);
+export function createCheckbox(props?: CreateCheckboxProps) {
+	const withDefaults = { ...defaults, ...props } satisfies CreateCheckboxProps;
+
+	const options = toWritableStores(omit(withDefaults, 'value', 'checked'));
+	const { disabled, name, required } = options;
+
+	// States
+	const value = writable<string | undefined>(withDefaults.value);
+	const checked = writable(withDefaults.checked);
 
 	const root = builder('checkbox', {
-		stores: [checked, options],
-		returned: ([$checked, $options]) => {
+		stores: [checked, disabled, required],
+		returned: ([$checked, $disabled, $required]) => {
 			return {
-				'data-disabled': $options.disabled,
+				'data-disabled': $disabled,
 				'data-state':
 					$checked === 'indeterminate' ? 'indeterminate' : $checked ? 'checked' : 'unchecked',
 				type: 'button',
 				role: 'checkbox',
 				'aria-checked': $checked === 'indeterminate' ? 'mixed' : $checked,
-				'aria-required': $options.required,
+				'aria-required': $required,
 			} as const;
 		},
 		action(node: HTMLElement) {
@@ -46,8 +50,7 @@ export function createCheckbox(props: CreateCheckboxProps = {}) {
 				})
 			);
 			addEventListener(node, 'click', () => {
-				const $options = get(options);
-				if ($options.disabled) return;
+				if (get(disabled)) return;
 
 				checked.update((value) => {
 					if (value === 'indeterminate') return true;
@@ -62,18 +65,18 @@ export function createCheckbox(props: CreateCheckboxProps = {}) {
 	});
 
 	const input = builder('checkbox-input', {
-		stores: [checked, options],
-		returned: ([$checked, $options]) => {
+		stores: [checked, name, value, required, disabled],
+		returned: ([$checked, $name, $value, $required, $disabled]) => {
 			return {
 				type: 'checkbox' as const,
 				'aria-hidden': true,
 				hidden: true,
 				tabindex: -1,
-				name: $options.name,
-				value: $options.value,
+				name: $name,
+				value: $value,
 				checked: $checked === 'indeterminate' ? false : $checked,
-				required: $options.required,
-				disabled: $options.disabled,
+				required: $required,
+				disabled: $disabled,
 				style: styleToString({
 					position: 'absolute',
 					opacity: 0,
@@ -89,11 +92,18 @@ export function createCheckbox(props: CreateCheckboxProps = {}) {
 	const isChecked = derived(checked, ($checked) => $checked === true);
 
 	return {
-		root,
-		input,
-		checked,
-		isIndeterminate,
-		isChecked,
+		elements: {
+			root,
+			input,
+		},
+		states: {
+			checked,
+			value,
+		},
+		helpers: {
+			isIndeterminate,
+			isChecked,
+		},
 		options,
 	};
 }
diff --git a/src/lib/builders/checkbox/types.ts b/src/lib/builders/checkbox/types.ts
index 2ad1ba6e..84b10606 100644
--- a/src/lib/builders/checkbox/types.ts
+++ b/src/lib/builders/checkbox/types.ts
@@ -1,4 +1,5 @@
 import type { createCheckbox } from './create';
+import type { BuilderReturn } from '$lib/internal/types';
 
 type CheckedState = boolean | 'indeterminate';
 
@@ -10,4 +11,9 @@ export type CreateCheckboxProps = {
 	value?: string;
 };
 
-export type CreateCheckboxReturn = ReturnType<typeof createCheckbox>;
+export type Checkbox = BuilderReturn<typeof createCheckbox>;
+
+export type CheckboxElements = Checkbox['elements'];
+export type CheckboxOptions = Checkbox['options'];
+export type CheckboxStates = Checkbox['states'];
+export type CheckboxHelpers = Checkbox['helpers'];
diff --git a/src/lib/builders/collapsible/create.ts b/src/lib/builders/collapsible/create.ts
index ff4712a9..232a9046 100644
--- a/src/lib/builders/collapsible/create.ts
+++ b/src/lib/builders/collapsible/create.ts
@@ -1,4 +1,10 @@
-import { addEventListener, builder, createElHelpers } from '$lib/internal/helpers';
+import {
+	addEventListener,
+	builder,
+	createElHelpers,
+	omit,
+	toWritableStores,
+} from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
 import { writable } from 'svelte/store';
 import type { CreateCollapsibleProps } from './types';
@@ -11,16 +17,18 @@ const defaults = {
 const { name } = createElHelpers('collapsible');
 
 export function createCollapsible(props?: CreateCollapsibleProps) {
-	const options = { ...defaults, ...props };
-	const disabled = writable(options.disabled);
+	const withDefaults = { ...defaults, ...props } satisfies CreateCollapsibleProps;
 
-	const open = writable(options.open);
+	const options = toWritableStores(omit(withDefaults, 'open'));
+	const { disabled } = options;
+
+	const open = writable(withDefaults.open);
 
 	const root = builder(name(), {
-		stores: open,
-		returned: ($open) => ({
+		stores: [open, disabled],
+		returned: ([$open, $disabled]) => ({
 			'data-state': $open ? 'open' : 'closed',
-			'data-disabled': options.disabled ? '' : 'undefined',
+			'data-disabled': $disabled ? '' : 'undefined',
 		}),
 	});
 
@@ -55,10 +63,14 @@ export function createCollapsible(props?: CreateCollapsibleProps) {
 	});
 
 	return {
-		root,
-		trigger,
-		content,
-		open,
-		disabled,
+		elements: {
+			root,
+			trigger,
+			content,
+		},
+		states: {
+			open,
+		},
+		options,
 	};
 }
diff --git a/src/lib/builders/collapsible/types.ts b/src/lib/builders/collapsible/types.ts
index 9ffa4456..1b9bba56 100644
--- a/src/lib/builders/collapsible/types.ts
+++ b/src/lib/builders/collapsible/types.ts
@@ -1,8 +1,12 @@
 import type { createCollapsible } from './create';
+import type { BuilderReturn } from '$lib/internal/types';
 
 export type CreateCollapsibleProps = {
 	open?: boolean;
 	disabled?: boolean;
 };
 
-export type CreateCollapsibleReturn = ReturnType<typeof createCollapsible>;
+export type Collapsible = BuilderReturn<typeof createCollapsible>;
+export type CollapsibleElements = Collapsible['elements'];
+export type CollapsibleOptions = Collapsible['options'];
+export type CollapsibleStates = Collapsible['states'];
diff --git a/src/lib/builders/combobox/create.ts b/src/lib/builders/combobox/create.ts
index 7c50db94..dca113ae 100644
--- a/src/lib/builders/combobox/create.ts
+++ b/src/lib/builders/combobox/create.ts
@@ -17,17 +17,18 @@ import {
 	last,
 	next,
 	noop,
-	omit,
 	prev,
 	removeScroll,
 	sleep,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import { getOptions } from '$lib/internal/helpers/list';
 import type { Defaults } from '$lib/internal/types';
 import { tick } from 'svelte';
 import { derived, get, readonly, writable } from 'svelte/store';
 import type { ComboboxItemProps, CreateComboboxProps } from './types';
+import { omit } from '../../internal/helpers/object';
 
 // prettier-ignore
 export const INTERACTION_KEYS = [kbd.ARROW_LEFT, kbd.ARROW_RIGHT, kbd.SHIFT, kbd.CAPS_LOCK, kbd.CONTROL, kbd.ALT, kbd.META, kbd.ENTER, kbd.F1, kbd.F2, kbd.F3, kbd.F4, kbd.F5, kbd.F6, kbd.F7, kbd.F8, kbd.F9, kbd.F10, kbd.F11, kbd.F12];
@@ -48,7 +49,8 @@ const { name, selector } = createElHelpers('combobox');
  * @TODO multi-select using `tags-input` builder?
  */
 export function createCombobox<T>(props: CreateComboboxProps<T>) {
-	const options = writable(omit({ ...defaults, ...props }, 'items'));
+	const withDefaults = { ...defaults, ...props } satisfies CreateComboboxProps<T>;
+
 	const open = writable(false);
 	// Trigger element for the popper portal. This will be our input element.
 	const activeTrigger = writable<HTMLElement | null>(null);
@@ -57,12 +59,16 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 	// The current value of the input element.
 	const inputValue = writable('');
 	// All items in the menu.
-	const items = writable(props.items);
+	const items = writable(withDefaults.items);
 	// A subset of items that match the filterFunction predicate.
-	const filteredItems = writable(props.items);
+	const filteredItems = writable(withDefaults.items);
 	// The currently selected menu item.
 	const selectedItem = writable<T>(undefined);
 
+	// options
+	const options = toWritableStores(omit(withDefaults, 'items'));
+	const { scrollAlignment, loop, filterFunction, itemToString } = options;
+
 	const ids = {
 		input: generateId(),
 		menu: generateId(),
@@ -93,14 +99,14 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 
 	/** Resets the combobox inputValue and filteredItems back to the selectedItem */
 	function reset() {
-		const $options = get(options);
+		const $itemToString = get(itemToString);
 		const $selectedItem = get(selectedItem);
 
 		// If no item is selected the input should be cleared and the filter reset.
 		if (!$selectedItem) {
 			inputValue.set('');
 		} else {
-			inputValue.set($options.itemToString($selectedItem));
+			inputValue.set($itemToString($selectedItem));
 		}
 		// Reset the filtered items to the full list.
 		filteredItems.set(get(items));
@@ -111,11 +117,11 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 	 * @param index array index of the item to select.
 	 */
 	function selectItem(item: HTMLElement) {
-		const $options = get(options);
+		const $itemToString = get(itemToString);
 		if (item.dataset.index) {
 			const index = parseInt(item.dataset.index, 10);
 			const $item = get(filteredItems)[index];
-			inputValue.set($options.itemToString($item));
+			inputValue.set($itemToString($item));
 
 			selectedItem.set($item);
 			// Reset the filtered items to the full list.
@@ -146,13 +152,13 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 	function updateItems(updaterFunction: (currentItems: T[]) => T[]): void {
 		const $currentItems = get(items);
 		const $inputValue = get(inputValue);
-		const $options = get(options);
+		const $filterFunction = get(filterFunction);
 		// Retrieve the updated list of items from the user-provided function.
 		const updatedItems = updaterFunction($currentItems);
 		// Update the store containing all items.
 		items.set(updatedItems);
 		// Run the filter function on the updated list and store the result.
-		filteredItems.set(updatedItems.filter((item) => $options.filterFunction(item, $inputValue)));
+		filteredItems.set(updatedItems.filter((item) => $filterFunction(item, $inputValue)));
 	}
 
 	/** Action and attributes for the text input. */
@@ -178,7 +184,6 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 				// Handle all input key events including typing, meta, and navigation.
 				addEventListener(node, 'keydown', (e) => {
 					const $open = get(open);
-					const $options = get(options);
 					/**
 					 * When the menu is closed...
 					 */
@@ -260,20 +265,21 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 						const $currentItem = get(highlightedItem);
 						const currentIndex = $currentItem ? candidateNodes.indexOf($currentItem) : -1;
 						// Find the next menu item to highlight.
-						const loop = $options.loop;
+						const $loop = get(loop);
+						const $scrollAlignment = get(scrollAlignment);
 						let nextItem: HTMLElement;
 						switch (e.key) {
 							case kbd.ARROW_DOWN:
-								nextItem = next(candidateNodes, currentIndex, loop);
+								nextItem = next(candidateNodes, currentIndex, $loop);
 								break;
 							case kbd.PAGE_DOWN:
-								nextItem = forward(candidateNodes, currentIndex, 10, loop);
+								nextItem = forward(candidateNodes, currentIndex, 10, $loop);
 								break;
 							case kbd.ARROW_UP:
-								nextItem = prev(candidateNodes, currentIndex, loop);
+								nextItem = prev(candidateNodes, currentIndex, $loop);
 								break;
 							case kbd.PAGE_UP:
-								nextItem = back(candidateNodes, currentIndex, 10, loop);
+								nextItem = back(candidateNodes, currentIndex, 10, $loop);
 								break;
 							case kbd.HOME:
 								nextItem = candidateNodes[0];
@@ -286,17 +292,17 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 						}
 						// Highlight the new item and scroll it into view.
 						highlightedItem.set(nextItem);
-						nextItem.scrollIntoView({ block: $options.scrollAlignment });
+						nextItem.scrollIntoView({ block: $scrollAlignment });
 					}
 				}),
 				// Listens to the input value and filters the items accordingly.
 				addEventListener(node, 'input', (e) => {
 					if (!isHTMLInputElement(e.target)) return;
-					const $options = get(options);
+					const $filterFunction = get(filterFunction);
 					const $items = get(items);
 					const value = e.target.value;
 					inputValue.set(value);
-					filteredItems.set($items.filter((item) => $options.filterFunction(item, value)));
+					filteredItems.set($items.filter((item) => $filterFunction(item, value)));
 				})
 			);
 			return { destroy: unsubscribe };
@@ -409,7 +415,7 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 	 * Handles moving the `data-highlighted` attribute between items when
 	 * the user moves their pointer or navigates with their keyboard.
 	 */
-	effect([highlightedItem, options], ([$highlightedItem, $options]) => {
+	effect([highlightedItem, scrollAlignment], ([$highlightedItem, $scrollAlignment]) => {
 		if (!isBrowser) return;
 		const menuElement = document.getElementById(ids.menu);
 		if (!isHTMLElement(menuElement)) return;
@@ -421,20 +427,26 @@ export function createCombobox<T>(props: CreateComboboxProps<T>) {
 			}
 		});
 		if ($highlightedItem) {
-			sleep(1).then(() => $highlightedItem.scrollIntoView({ block: $options.scrollAlignment }));
+			sleep(1).then(() => $highlightedItem.scrollIntoView({ block: $scrollAlignment }));
 		}
 	});
 
 	return {
-		filteredItems: readonly(filteredItems),
-		updateItems,
-		inputValue,
-		isSelected,
-		selectedItem,
+		elements: {
+			input,
+			item,
+			menu,
+		},
+		states: {
+			open,
+			inputValue,
+			filteredItems: readonly(filteredItems),
+			selectedItem,
+		},
+		helpers: {
+			updateItems,
+			isSelected,
+		},
 		options,
-		open,
-		menu,
-		input,
-		item,
 	};
 }
diff --git a/src/lib/builders/combobox/types.ts b/src/lib/builders/combobox/types.ts
index cc19690e..894cec89 100644
--- a/src/lib/builders/combobox/types.ts
+++ b/src/lib/builders/combobox/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createCombobox } from './create';
 
 export type CreateComboboxProps<T> = {
@@ -16,11 +17,15 @@ export type CreateComboboxProps<T> = {
 	 * @param value the current input value.
 	 * @returns whether the item should be visible.
 	 */
-	filterFunction: (item: T, value: string) => boolean;
-	itemToString: (item: T) => string;
+	filterFunction: ComboboxFilterFunction<T>;
+	itemToString: ComboboxItemToString<T>;
 	loop?: boolean;
 };
 
+export type ComboboxFilterFunction<T> = (item: T, value: string) => boolean;
+
+export type ComboboxItemToString<T> = (item: T) => string;
+
 export type ComboboxItemProps<T> = {
 	item: T;
 	/** Array index of the item. */
@@ -29,31 +34,8 @@ export type ComboboxItemProps<T> = {
 	disabled?: boolean;
 };
 
-// export type CreateComboboxReturn<T> = {
-// 	/** List items to display that match the current filter. */
-// 	filteredItems: Readable<T[]>;
-// 	/** Action & attributes to apply to the input element. */
-// 	input: Readable<HTMLInputAttributes & Action<HTMLInputElement, void>> &
-// 		Action<HTMLInputElement, void>;
-// 	/** Current input value. */
-// 	inputValue: Writable<string>;
-// 	/** Function to determine if a given item is selected. */
-// 	isSelected: Readable<(item: T) => boolean>;
-// 	/** Action & attributes to apply to each item in the menu. */
-// 	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: improve this type
-// 	item: Readable<(args: ComboboxItemArgs<T>) => any> & Action<HTMLElement, void>;
-// 	/** Action & attributes to apply to the menu. */
-// 	menu: Readable<HTMLAttributes<HTMLElement & Action<HTMLInputElement, void>>> &
-// 		Action<HTMLElement, void>;
-// 	/** Is the menu currently open? */
-// 	open: Writable<boolean>;
-// 	/** Top-level configuration for the Combobox instance. */
-// 	options: Writable<Omit<CreateComboboxArgs<T>, 'items'>>;
-// 	/** The currently selected item. */
-// 	selectedItem: Writable<T | undefined>;
-// 	/** Function to update the items in the combobox. */
-// 	updateItems: (updaterFunction: (currentItems: T[]) => T[]) => void;
-// };
-
-// TODO: improve type
-export type CreateComboboxReturn = ReturnType<typeof createCombobox>;
+export type Combobox = BuilderReturn<typeof createCombobox>;
+export type ComboboxElements = Combobox['elements'];
+export type ComboboxOptions = Combobox['options'];
+export type ComboboxStates = Combobox['states'];
+export type ComboboxHelpers = Combobox['helpers'];
diff --git a/src/lib/builders/context-menu/create.ts b/src/lib/builders/context-menu/create.ts
index 61646d24..6e3b9e2e 100644
--- a/src/lib/builders/context-menu/create.ts
+++ b/src/lib/builders/context-menu/create.ts
@@ -14,6 +14,7 @@ import {
 	noop,
 	styleToString,
 	isLeftClick,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
 import type { VirtualElement } from '@floating-ui/core';
@@ -38,13 +39,18 @@ const defaults = {
 		placement: 'bottom-start',
 	},
 	preventScroll: true,
+	loop: false,
+	dir: 'ltr',
 } satisfies Defaults<CreateContextMenuProps>;
 
 const { name, selector } = createElHelpers<MenuParts>('context-menu');
 
 export function createContextMenu(props?: CreateContextMenuProps) {
 	const withDefaults = { ...defaults, ...props } satisfies CreateContextMenuProps;
-	const rootOptions = writable(withDefaults);
+
+	const rootOptions = toWritableStores(withDefaults);
+	const { positioning } = rootOptions;
+
 	const rootOpen = writable(false);
 	const rootActiveTrigger = writable<HTMLElement | null>(null);
 	const nextFocusable = writable<HTMLElement | null>(null);
@@ -54,7 +60,7 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 		item,
 		checkboxItem,
 		arrow,
-		createSubMenu,
+		createSubmenu,
 		createMenuRadioGroup,
 		rootIds,
 		separator,
@@ -102,8 +108,8 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 			let unsubPopper = noop;
 
 			const unsubDerived = effect(
-				[rootOpen, rootActiveTrigger, rootOptions],
-				([$rootOpen, $rootActiveTrigger, $rootOptions]) => {
+				[rootOpen, rootActiveTrigger, positioning],
+				([$rootOpen, $rootActiveTrigger, $positioning]) => {
 					unsubPopper();
 					if ($rootOpen && $rootActiveTrigger) {
 						tick().then(() => {
@@ -114,7 +120,7 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 								anchorElement: $virtual,
 								open: rootOpen,
 								options: {
-									floating: $rootOptions.positioning,
+									floating: $positioning,
 									clickOutside: {
 										handler: (e: PointerEvent) => {
 											if (e.defaultPrevented) return;
@@ -268,16 +274,22 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 	});
 
 	return {
-		open: rootOpen,
+		elements: {
+			menu,
+			trigger,
+			item,
+			checkboxItem,
+			arrow,
+			separator,
+		},
+		states: {
+			open: rootOpen,
+		},
+		builders: {
+			createSubmenu,
+			createMenuRadioGroup,
+		},
 		options: rootOptions,
-		menu,
-		trigger,
-		item,
-		checkboxItem,
-		arrow,
-		separator,
-		createSubMenu,
-		createMenuRadioGroup,
 	};
 }
 
diff --git a/src/lib/builders/context-menu/types.ts b/src/lib/builders/context-menu/types.ts
index 512671e5..2504238b 100644
--- a/src/lib/builders/context-menu/types.ts
+++ b/src/lib/builders/context-menu/types.ts
@@ -1,6 +1,8 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { Menu } from '../menu';
 import type { createContextMenu } from './create';
 
+// Props
 export type CreateContextMenuProps = Menu['builder'];
 export type CreateContextSubmenuProps = Menu['submenu'];
 export type ContextMenuItemProps = Menu['item'];
@@ -9,4 +11,19 @@ export type CreateContextMenuRadioGroupProps = Menu['radioGroup'];
 export type ContextMenuRadioItemProps = Menu['radioItem'];
 export type ContextMenuRadioItemActionProps = Menu['radioItemAction'];
 
-export type CreateContextMenuReturn = ReturnType<typeof createContextMenu>;
+// Returns
+export type ContextMenu = BuilderReturn<typeof createContextMenu>;
+export type ContextMenuElements = ContextMenu['elements'];
+export type ContextMenuOptions = ContextMenu['options'];
+export type ContextMenuStates = ContextMenu['states'];
+export type ContextMenuBuilders = ContextMenu['builders'];
+
+export type ContextMenuSubmenu = BuilderReturn<ContextMenuBuilders['createSubmenu']>;
+export type ContextMenuSubmenuElements = ContextMenuSubmenu['elements'];
+export type ContextMenuSubmenuOptions = ContextMenuSubmenu['options'];
+export type ContextMenuSubmenuStates = ContextMenuSubmenu['states'];
+
+export type ContextMenuRadioGroup = BuilderReturn<ContextMenuBuilders['createMenuRadioGroup']>;
+export type ContextMenuRadioGroupElements = ContextMenuRadioGroup['elements'];
+export type ContextMenuRadioGroupStates = ContextMenuRadioGroup['states'];
+export type ContextMenuRadioGroupHelpers = ContextMenuRadioGroup['helpers'];
diff --git a/src/lib/builders/dialog/create.ts b/src/lib/builders/dialog/create.ts
index 0523ff32..d119da89 100644
--- a/src/lib/builders/dialog/create.ts
+++ b/src/lib/builders/dialog/create.ts
@@ -11,6 +11,7 @@ import {
 	noop,
 	sleep,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import { removeScroll } from '$lib/internal/helpers/scroll';
 import type { Defaults } from '$lib/internal/types';
@@ -30,8 +31,11 @@ const defaults = {
 const openDialogIds = writable<string[]>([]);
 
 export function createDialog(props: CreateDialogProps = {}) {
-	const withDefaults = { ...defaults, ...props };
-	const options = writable({ ...withDefaults });
+	const withDefaults = { ...defaults, ...props } satisfies CreateDialogProps;
+
+	const options = toWritableStores(withDefaults);
+	const { preventScroll, closeOnEscape, closeOnOutsideClick, role } = options;
+
 	const activeTrigger = writable<HTMLElement | null>(null);
 
 	const ids = {
@@ -92,11 +96,11 @@ export function createDialog(props: CreateDialogProps = {}) {
 	});
 
 	const content = builder(name('content'), {
-		stores: open,
-		returned: ($open) => {
+		stores: [open, role],
+		returned: ([$open, $role]) => {
 			return {
 				id: ids.content,
-				role: 'dialog',
+				role: $role,
 				'aria-describedby': ids.description,
 				'aria-labelledby': ids.title,
 				'data-state': $open ? 'open' : 'closed',
@@ -119,11 +123,11 @@ export function createDialog(props: CreateDialogProps = {}) {
 						return false;
 					}
 
-					const $options = get(options);
+					const $closeOnOutsideClick = get(closeOnOutsideClick);
 					const $openDialogIds = get(openDialogIds);
 					const isLast = last($openDialogIds) === ids.content;
 
-					if ($options.closeOnOutsideClick && isLast) {
+					if ($closeOnOutsideClick && isLast) {
 						open.set(false);
 					}
 
@@ -181,27 +185,30 @@ export function createDialog(props: CreateDialogProps = {}) {
 		},
 	});
 
-	effect([open, options, openDialogIds], ([$open, $options, $openDialogIds]) => {
-		const unsubs: Array<() => void> = [];
+	effect(
+		[open, openDialogIds, closeOnEscape, preventScroll],
+		([$open, $openDialogIds, $closeOnEscape, $preventScroll]) => {
+			const unsubs: Array<() => void> = [];
+
+			const isLast = last($openDialogIds) === ids.content;
+
+			if ($closeOnEscape && $open && isLast) {
+				unsubs.push(
+					addEventListener(document, 'keydown', (e) => {
+						if (e.key === 'Escape') {
+							open.set(false);
+						}
+					})
+				);
+			}
 
-		const isLast = last($openDialogIds) === ids.content;
+			if ($preventScroll && $open) unsubs.push(removeScroll());
 
-		if ($options.closeOnEscape && $open && isLast) {
-			unsubs.push(
-				addEventListener(document, 'keydown', (e) => {
-					if (e.key === 'Escape') {
-						open.set(false);
-					}
-				})
-			);
+			return () => {
+				unsubs.forEach((unsub) => unsub());
+			};
 		}
-
-		if ($options.preventScroll && $open) unsubs.push(removeScroll());
-
-		return () => {
-			unsubs.forEach((unsub) => unsub());
-		};
-	});
+	);
 
 	effect([open, activeTrigger], ([$open, $activeTrigger]) => {
 		if (!isBrowser) return;
@@ -213,14 +220,20 @@ export function createDialog(props: CreateDialogProps = {}) {
 	});
 
 	return {
+		elements: {
+			content,
+			trigger,
+			title,
+			description,
+			overlay,
+			close,
+		},
+		states: {
+			open,
+		},
+		actions: {
+			portal: usePortal,
+		},
 		options,
-		open,
-		trigger,
-		overlay,
-		portal: usePortal,
-		content,
-		title,
-		description,
-		close,
 	};
 }
diff --git a/src/lib/builders/dialog/types.ts b/src/lib/builders/dialog/types.ts
index efc9e268..ff0ac5d7 100644
--- a/src/lib/builders/dialog/types.ts
+++ b/src/lib/builders/dialog/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createDialog } from './create';
 
 export type CreateDialogProps = {
@@ -7,4 +8,8 @@ export type CreateDialogProps = {
 	role?: 'dialog' | 'alertdialog';
 };
 
-export type CreateDialogReturn = ReturnType<typeof createDialog>;
+export type Dialog = BuilderReturn<typeof createDialog>;
+export type DialogElements = Dialog['elements'];
+export type DialogOptions = Dialog['options'];
+export type DialogStates = Dialog['states'];
+export type DialogActions = Dialog['actions'];
diff --git a/src/lib/builders/dropdown-menu/create.ts b/src/lib/builders/dropdown-menu/create.ts
index 367b1dcc..1edea7cc 100644
--- a/src/lib/builders/dropdown-menu/create.ts
+++ b/src/lib/builders/dropdown-menu/create.ts
@@ -2,6 +2,7 @@ import type { Defaults } from '$lib/internal/types';
 import { writable } from 'svelte/store';
 import { createMenuBuilder } from '../menu';
 import type { CreateDropdownMenuProps } from './types';
+import { toWritableStores } from '@melt-ui/svelte/internal/helpers';
 
 const defaults = {
 	arrowSize: 8,
@@ -9,11 +10,15 @@ const defaults = {
 		placement: 'bottom',
 	},
 	preventScroll: true,
+	loop: false,
+	dir: 'ltr',
 } satisfies Defaults<CreateDropdownMenuProps>;
 
 export function createDropdownMenu(props?: CreateDropdownMenuProps) {
-	const withDefaults = { ...defaults, ...props } as CreateDropdownMenuProps;
-	const rootOptions = writable(withDefaults);
+	const withDefaults = { ...defaults, ...props } satisfies CreateDropdownMenuProps;
+
+	const rootOptions = toWritableStores(withDefaults);
+
 	const rootOpen = writable(false);
 	const rootActiveTrigger = writable<HTMLElement | null>(null);
 	const nextFocusable = writable<HTMLElement | null>(null);
@@ -25,7 +30,7 @@ export function createDropdownMenu(props?: CreateDropdownMenuProps) {
 		item,
 		checkboxItem,
 		arrow,
-		createSubMenu,
+		createSubmenu,
 		createMenuRadioGroup,
 		separator,
 	} = createMenuBuilder({
@@ -39,15 +44,21 @@ export function createDropdownMenu(props?: CreateDropdownMenuProps) {
 	});
 
 	return {
-		trigger,
-		menu,
-		open: rootOpen,
-		item,
-		checkboxItem,
-		arrow,
+		elements: {
+			trigger,
+			menu,
+			item,
+			checkboxItem,
+			arrow,
+			separator,
+		},
+		states: {
+			open: rootOpen,
+		},
+		builders: {
+			createSubmenu,
+			createMenuRadioGroup,
+		},
 		options: rootOptions,
-		createSubMenu,
-		createMenuRadioGroup,
-		separator,
 	};
 }
diff --git a/src/lib/builders/dropdown-menu/types.ts b/src/lib/builders/dropdown-menu/types.ts
index 00c9d000..62215a39 100644
--- a/src/lib/builders/dropdown-menu/types.ts
+++ b/src/lib/builders/dropdown-menu/types.ts
@@ -1,6 +1,8 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { Menu } from '../menu';
 import type { createDropdownMenu } from './create';
 
+// Props
 export type CreateDropdownMenuProps = Menu['builder'];
 export type CreateDropdownSubmenuProps = Menu['submenu'];
 export type DropdownMenuItemProps = Menu['item'];
@@ -9,4 +11,19 @@ export type CreateDropdownMenuRadioGroupProps = Menu['radioGroup'];
 export type DropdownMenuRadioItemProps = Menu['radioItem'];
 export type DropdownMenuRadioItemActionProps = Menu['radioItemAction'];
 
-export type CreateDropdownMenuReturn = ReturnType<typeof createDropdownMenu>;
+// Returns
+export type DropdownMenu = BuilderReturn<typeof createDropdownMenu>;
+export type DropdownMenuElements = DropdownMenu['elements'];
+export type DropdownMenuOptions = DropdownMenu['options'];
+export type DropdownMenuStates = DropdownMenu['states'];
+export type DropdownMenuBuilders = DropdownMenu['builders'];
+
+export type DropdownMenuSubmenu = BuilderReturn<DropdownMenuBuilders['createSubmenu']>;
+export type DropdownMenuSubmenuElements = DropdownMenuSubmenu['elements'];
+export type DropdownMenuSubmenuOptions = DropdownMenuSubmenu['options'];
+export type DropdownMenuSubmenuStates = DropdownMenuSubmenu['states'];
+
+export type DropdownMenuRadioGroup = BuilderReturn<DropdownMenuBuilders['createMenuRadioGroup']>;
+export type DropdownMenuRadioGroupElements = DropdownMenuRadioGroup['elements'];
+export type DropdownMenuRadioGroupStates = DropdownMenuRadioGroup['states'];
+export type DropdownMenuRadioGroupHelpers = DropdownMenuRadioGroup['helpers'];
diff --git a/src/lib/builders/hover-card/create.ts b/src/lib/builders/hover-card/create.ts
index 408916bb..6f9b02c3 100644
--- a/src/lib/builders/hover-card/create.ts
+++ b/src/lib/builders/hover-card/create.ts
@@ -13,6 +13,7 @@ import {
 	noop,
 	sleep,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
 import { tick } from 'svelte';
@@ -34,13 +35,17 @@ const defaults = {
 } satisfies Defaults<CreateHoverCardProps>;
 
 export function createHoverCard(props: CreateHoverCardProps = {}) {
-	const propsWithDefaults = { ...defaults, ...props } satisfies CreateHoverCardProps;
-	const options = writable(propsWithDefaults);
-	const open = writable(propsWithDefaults.defaultOpen);
+	const withDefaults = { ...defaults, ...props } satisfies CreateHoverCardProps;
+	const open = writable(withDefaults.defaultOpen);
 	const hasSelection = writable(false);
 	const isPointerDownOnContent = writable(false);
 	const containSelection = writable(false);
 
+	// options
+	const options = toWritableStores(withDefaults);
+
+	const { openDelay, closeDelay, positioning, arrowSize, closeOnOutsideClick } = options;
+
 	const ids = {
 		content: generateId(),
 		trigger: generateId(),
@@ -49,7 +54,7 @@ export function createHoverCard(props: CreateHoverCardProps = {}) {
 	let timeout: number | null = null;
 	let originalBodyUserSelect: string;
 
-	const handleOpen = derived(options, ($options) => {
+	const handleOpen = derived(openDelay, ($openDelay) => {
 		return () => {
 			if (timeout) {
 				window.clearTimeout(timeout);
@@ -58,13 +63,13 @@ export function createHoverCard(props: CreateHoverCardProps = {}) {
 
 			timeout = window.setTimeout(() => {
 				open.set(true);
-			}, $options.openDelay);
+			}, $openDelay);
 		};
 	}) as Readable<() => void>;
 
 	const handleClose = derived(
-		[options, isPointerDownOnContent, hasSelection],
-		([$options, $isPointerDownOnContent, $hasSelection]) => {
+		[closeDelay, isPointerDownOnContent, hasSelection],
+		([$closeDelay, $isPointerDownOnContent, $hasSelection]) => {
 			return () => {
 				if (timeout) {
 					window.clearTimeout(timeout);
@@ -73,7 +78,7 @@ export function createHoverCard(props: CreateHoverCardProps = {}) {
 				if (!$isPointerDownOnContent && !$hasSelection) {
 					timeout = window.setTimeout(() => {
 						open.set(false);
-					}, $options.closeDelay);
+					}, $closeDelay);
 				}
 			};
 		}
@@ -148,15 +153,16 @@ export function createHoverCard(props: CreateHoverCardProps = {}) {
 					tick().then(() => {
 						const triggerEl = document.getElementById(ids.trigger);
 						if (!triggerEl || node.hidden) return;
-						const $options = get(options);
+						const $positioning = get(positioning);
+						const $closeOnOutsideClick = get(closeOnOutsideClick);
 
 						const popper = usePopper(node, {
 							anchorElement: triggerEl,
 							open,
 							options: {
-								floating: $options.positioning,
+								floating: $positioning,
 								focusTrap: null,
-								clickOutside: !$options.closeOnOutsideClick ? null : undefined,
+								clickOutside: !$closeOnOutsideClick ? null : undefined,
 							},
 						});
 						if (popper && popper.destroy) {
@@ -209,13 +215,13 @@ export function createHoverCard(props: CreateHoverCardProps = {}) {
 	});
 
 	const arrow = builder(name('arrow'), {
-		stores: options,
-		returned: ($options) => ({
+		stores: arrowSize,
+		returned: ($arrowSize) => ({
 			'data-arrow': true,
 			style: styleToString({
 				position: 'absolute',
-				width: `var(--arrow-size, ${$options.arrowSize}px)`,
-				height: `var(--arrow-size, ${$options.arrowSize}px)`,
+				width: `var(--arrow-size, ${$arrowSize}px)`,
+				height: `var(--arrow-size, ${$arrowSize}px)`,
 			}),
 		}),
 	});
@@ -274,5 +280,15 @@ export function createHoverCard(props: CreateHoverCardProps = {}) {
 		}
 	});
 
-	return { trigger, open, content, arrow, options };
+	return {
+		elements: {
+			trigger,
+			content,
+			arrow,
+		},
+		states: {
+			open,
+		},
+		options,
+	};
 }
diff --git a/src/lib/builders/hover-card/types.ts b/src/lib/builders/hover-card/types.ts
index 3ba90877..f940856f 100644
--- a/src/lib/builders/hover-card/types.ts
+++ b/src/lib/builders/hover-card/types.ts
@@ -1,4 +1,5 @@
 import type { FloatingConfig } from '$lib/internal/actions';
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createHoverCard } from './create';
 
 export type CreateHoverCardProps = {
@@ -10,4 +11,7 @@ export type CreateHoverCardProps = {
 	arrowSize?: number;
 };
 
-export type CreateHoverCardReturn = ReturnType<typeof createHoverCard>;
+export type HoverCard = BuilderReturn<typeof createHoverCard>;
+export type HoverCardElements = HoverCard['elements'];
+export type HoverCardOptions = HoverCard['options'];
+export type HoverCardStates = HoverCard['states'];
diff --git a/src/lib/builders/label/create.ts b/src/lib/builders/label/create.ts
index 553b02f2..6060d78e 100644
--- a/src/lib/builders/label/create.ts
+++ b/src/lib/builders/label/create.ts
@@ -15,5 +15,9 @@ export function createLabel() {
 		},
 	});
 
-	return root;
+	return {
+		elements: {
+			root,
+		},
+	};
 }
diff --git a/src/lib/builders/label/types.ts b/src/lib/builders/label/types.ts
index c887d7d2..a52f0dd1 100644
--- a/src/lib/builders/label/types.ts
+++ b/src/lib/builders/label/types.ts
@@ -1,3 +1,5 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createLabel } from './create';
 
-export type CreateLabelReturn = ReturnType<typeof createLabel>;
+export type Label = BuilderReturn<typeof createLabel>;
+export type LabelElements = Label['elements'];
diff --git a/src/lib/builders/menu/create.ts b/src/lib/builders/menu/create.ts
index 4cb74dbd..b7e75c39 100644
--- a/src/lib/builders/menu/create.ts
+++ b/src/lib/builders/menu/create.ts
@@ -60,7 +60,16 @@ const defaults = {
 export function createMenuBuilder(opts: MenuBuilderOptions) {
 	const { name, selector } = createElHelpers<MenuParts>(opts.selector);
 
-	const rootOptions = opts.rootOptions;
+	const { preventScroll, loop, arrowSize, dir, positioning } = opts.rootOptions;
+
+	const rootOptions = {
+		positioning,
+		arrowSize,
+		dir,
+		preventScroll,
+		loop,
+	};
+
 	const rootOpen = opts.rootOpen;
 	const rootActiveTrigger = opts.rootActiveTrigger;
 	/**
@@ -132,8 +141,8 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 			let unsubPopper = noop;
 
 			const unsubDerived = effect(
-				[rootOpen, rootActiveTrigger, rootOptions],
-				([$rootOpen, $rootActiveTrigger, $rootOptions]) => {
+				[rootOpen, rootActiveTrigger, positioning],
+				([$rootOpen, $rootActiveTrigger, $positioning]) => {
 					unsubPopper();
 					if ($rootOpen && $rootActiveTrigger) {
 						tick().then(() => {
@@ -142,7 +151,7 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 								anchorElement: $rootActiveTrigger,
 								open: rootOpen,
 								options: {
-									floating: $rootOptions.positioning,
+									floating: $positioning,
 								},
 							});
 
@@ -290,13 +299,13 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 	});
 
 	const rootArrow = builder(name('arrow'), {
-		stores: rootOptions,
-		returned: ($rootOptions) => ({
+		stores: arrowSize,
+		returned: ($arrowSize) => ({
 			'data-arrow': true,
 			style: styleToString({
 				position: 'absolute',
-				width: `var(--arrow-size, ${$rootOptions.arrowSize}px)`,
-				height: `var(--arrow-size, ${$rootOptions.arrowSize}px)`,
+				width: `var(--arrow-size, ${$arrowSize}px)`,
+				height: `var(--arrow-size, ${$arrowSize}px)`,
 			}),
 		}),
 	});
@@ -557,14 +566,22 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 		});
 
 		return {
-			radioGroup,
-			radioItem,
-			isChecked,
-			value,
+			elements: {
+				radioGroup,
+				radioItem,
+			},
+			states: {
+				value,
+			},
+			helpers: {
+				isChecked,
+			},
 		};
 	};
 
-	const { root: separator } = createSeparator({
+	const {
+		elements: { root: separator },
+	} = createSeparator({
 		orientation: 'horizontal',
 	});
 
@@ -581,11 +598,28 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 		},
 	} satisfies Defaults<CreateSubmenuProps>;
 
-	const createSubMenu = (args?: CreateSubmenuProps) => {
-		const withDefaults = { ...subMenuDefaults, ...args } as CreateSubmenuProps;
-		const subOptions = writable(withDefaults);
+	const createSubmenu = (args?: CreateSubmenuProps) => {
+		const withDefaults = { ...subMenuDefaults, ...args } satisfies CreateSubmenuProps;
 
 		const subOpen = writable(false);
+
+		// options
+		const positioning = writable<FloatingConfig>(withDefaults.positioning);
+		const arrowSize = writable<number>(withDefaults.arrowSize);
+		const dir = writable(withDefaults.dir);
+		const disabled = writable<boolean>(withDefaults.disabled);
+		const preventScroll = writable<boolean>(withDefaults.preventScroll);
+		const loop = writable<boolean>(withDefaults.loop);
+
+		const subOptions = {
+			positioning,
+			arrowSize,
+			dir,
+			disabled,
+			preventScroll,
+			loop,
+		};
+
 		const subActiveTrigger = writable<HTMLElement | null>(null);
 		const subOpenTimer = writable<number | null>(null);
 		const pointerGraceTimer = writable(0);
@@ -614,8 +648,8 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 				let unsubPopper = noop;
 
 				const unsubDerived = effect(
-					[subOpen, subActiveTrigger, subOptions],
-					([$subOpen, $subActiveTrigger, $subOptions]) => {
+					[subOpen, subActiveTrigger, positioning],
+					([$subOpen, $subActiveTrigger, $positioning]) => {
 						unsubPopper();
 						if ($subOpen && $subActiveTrigger) {
 							tick().then(() => {
@@ -625,7 +659,7 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 									anchorElement: $subActiveTrigger,
 									open: subOpen,
 									options: {
-										floating: $subOptions.positioning,
+										floating: $positioning,
 										portal: isHTMLElement(parentMenuEl) ? parentMenuEl : undefined,
 										clickOutside: null,
 										focusTrap: null,
@@ -741,8 +775,8 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 		});
 
 		const subTrigger = builder(name('subtrigger'), {
-			stores: [subOpen, subOptions],
-			returned: ([$subOpen, $subOptions]) => {
+			stores: [subOpen, disabled],
+			returned: ([$subOpen, $disabled]) => {
 				return {
 					role: 'menuitem',
 					id: subIds.trigger,
@@ -750,7 +784,7 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 					'aria-controls': subIds.menu,
 					'aria-expanded': $subOpen,
 					'data-state': $subOpen ? 'open' : 'closed',
-					'data-disabled': $subOptions.disabled ? '' : undefined,
+					'data-disabled': $disabled ? '' : undefined,
 					'aria-haspopop': 'menu',
 				} as const;
 			},
@@ -910,13 +944,13 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 		});
 
 		const subArrow = builder(name('subarrow'), {
-			stores: subOptions,
-			returned: ($subOptions) => ({
+			stores: arrowSize,
+			returned: ($arrowSize) => ({
 				'data-arrow': true,
 				style: styleToString({
 					position: 'absolute',
-					width: `var(--arrow-size, ${$subOptions.arrowSize}px)`,
-					height: `var(--arrow-size, ${$subOptions.arrowSize}px)`,
+					width: `var(--arrow-size, ${$arrowSize}px)`,
+					height: `var(--arrow-size, ${$arrowSize}px)`,
 				}),
 			}),
 		});
@@ -975,11 +1009,15 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 		});
 
 		return {
-			subTrigger,
-			subMenu,
-			subOpen,
-			subArrow,
-			subOptions,
+			elements: {
+				subTrigger,
+				subMenu,
+				subArrow,
+			},
+			states: {
+				subOpen,
+			},
+			options: subOptions,
 		};
 	};
 
@@ -997,9 +1035,9 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 		if (!isBrowser) return;
 
 		const unsubs: Array<() => void> = [];
-		const $rootOptions = get(rootOptions);
+		const $preventScroll = get(preventScroll);
 
-		if ($rootOpen && $rootOptions.preventScroll) {
+		if ($rootOpen && $preventScroll) {
 			unsubs.push(removeScroll());
 		}
 
@@ -1207,7 +1245,7 @@ export function createMenuBuilder(opts: MenuBuilderOptions) {
 		checkboxItem,
 		arrow: rootArrow,
 		options: rootOptions,
-		createSubMenu,
+		createSubmenu,
 		createMenuRadioGroup,
 		separator,
 		rootIds,
diff --git a/src/lib/builders/menu/types.ts b/src/lib/builders/menu/types.ts
index 2f7c4cee..a2200f55 100644
--- a/src/lib/builders/menu/types.ts
+++ b/src/lib/builders/menu/types.ts
@@ -75,7 +75,13 @@ export type Menu = {
 export type MenuBuilderOptions = {
 	rootOpen: Writable<boolean>;
 	rootActiveTrigger: Writable<HTMLElement | null>;
-	rootOptions: Writable<CreateMenuProps>;
+	rootOptions: {
+		positioning: Writable<FloatingConfig>;
+		arrowSize: Writable<number | undefined>;
+		preventScroll: Writable<boolean | undefined>;
+		loop: Writable<boolean | undefined>;
+		dir: Writable<TextDirection>;
+	};
 	disableTriggerRefocus?: boolean;
 	disableFocusFirstItem?: boolean;
 	nextFocusable: Writable<HTMLElement | null>;
diff --git a/src/lib/builders/menubar/create.ts b/src/lib/builders/menubar/create.ts
index 422a6ed6..698b1d2d 100644
--- a/src/lib/builders/menubar/create.ts
+++ b/src/lib/builders/menubar/create.ts
@@ -7,6 +7,8 @@ import {
 	handleMenuNavigation,
 	handleTabNavigation,
 	type MenuParts,
+	removeHighlight,
+	addHighlight,
 } from '../menu';
 import {
 	executeCallbacks,
@@ -26,6 +28,7 @@ import {
 	builder,
 	createElHelpers,
 	isLeftClick,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import { onMount, tick } from 'svelte';
 import { usePopper } from '$lib/internal/actions';
@@ -41,6 +44,9 @@ const defaults = {
 
 export function createMenubar(props?: CreateMenubarProps) {
 	const withDefaults = { ...defaults, ...props } satisfies CreateMenubarProps;
+
+	const options = toWritableStores(withDefaults);
+	const { loop } = options;
 	const activeMenu = writable<string>('');
 	const scopedMenus = writable<HTMLElement[]>([]);
 	const nextFocusable = writable<HTMLElement | null>(null);
@@ -60,13 +66,13 @@ export function createMenubar(props?: CreateMenubarProps) {
 			};
 		},
 		action: (node: HTMLElement) => {
-			const menuTriggers = node.querySelectorAll(
-				'[data-melt-menubar-trigger]'
-			) as unknown as HTMLElement[];
+			const menuTriggers = Array.from(
+				node.querySelectorAll<HTMLElement>('[data-melt-menubar-trigger]')
+			);
 			if (menuTriggers.length === 0) return;
 			menuTriggers[0].tabIndex = 0;
 
-			const menus = Array.from(node.querySelectorAll('[data-melt-menubar-menu]')) as HTMLElement[];
+			const menus = Array.from(node.querySelectorAll<HTMLElement>('[data-melt-menubar-menu]'));
 			scopedMenus.set(menus);
 
 			return {
@@ -80,16 +86,22 @@ export function createMenubar(props?: CreateMenubarProps) {
 			placement: 'bottom-start',
 		},
 		preventScroll: true,
+		arrowSize: 8,
+		dir: 'ltr',
+		loop: false,
 	} satisfies Defaults<CreateMenubarMenuProps>;
 
 	const createMenu = (props?: CreateMenubarMenuProps) => {
-		const withDefaults = { ...menuDefaults, ...props } as CreateMenubarMenuProps;
-		const rootOptions = writable(withDefaults);
+		const withDefaults = { ...menuDefaults, ...props } satisfies CreateMenubarMenuProps;
 		const rootOpen = writable(false);
 		const rootActiveTrigger = writable<HTMLElement | null>(null);
 
+		// options
+		const options = toWritableStores(withDefaults);
+		const { positioning } = options;
+
 		const m = createMenuBuilder({
-			rootOptions,
+			rootOptions: options,
 			rootOpen,
 			rootActiveTrigger,
 			disableTriggerRefocus: true,
@@ -119,8 +131,8 @@ export function createMenubar(props?: CreateMenubarProps) {
 				let unsubPopper = noop;
 
 				const unsubDerived = effect(
-					[rootOpen, rootActiveTrigger, rootOptions],
-					([$rootOpen, $rootActiveTrigger, $rootOptions]) => {
+					[rootOpen, rootActiveTrigger, positioning],
+					([$rootOpen, $rootActiveTrigger, $positioning]) => {
 						unsubPopper();
 						if ($rootOpen && $rootActiveTrigger) {
 							tick().then(() => {
@@ -128,7 +140,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 									anchorElement: $rootActiveTrigger,
 									open: rootOpen,
 									options: {
-										floating: $rootOptions.positioning,
+										floating: $positioning,
 									},
 								});
 
@@ -318,7 +330,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 				const triggerElement = document.getElementById(m.rootIds.trigger);
 				if (!isHTMLElement(triggerElement)) return;
 				rootActiveTrigger.set(triggerElement);
-				triggerElement.setAttribute('data-highlighted', '');
+				addHighlight(triggerElement);
 				rootOpen.set(true);
 				return;
 			}
@@ -328,7 +340,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 				if (get(rootOpen)) {
 					const triggerElement = document.getElementById(m.rootIds.trigger);
 					if (!isHTMLElement(triggerElement)) return;
-					triggerElement.removeAttribute('data-highlighted');
+					removeHighlight(triggerElement);
 					rootActiveTrigger.set(null);
 					rootOpen.set(false);
 				}
@@ -341,23 +353,35 @@ export function createMenubar(props?: CreateMenubarProps) {
 			const triggerElement = document.getElementById(m.rootIds.trigger);
 			if (!$rootOpen && get(activeMenu) === m.rootIds.menu) {
 				activeMenu.set('');
-				triggerElement?.removeAttribute('data-highlighted');
+				if (triggerElement) {
+					removeHighlight(triggerElement);
+				}
 				return;
 			}
 			if ($rootOpen) {
-				triggerElement?.setAttribute('data-highlighted', '');
+				if (triggerElement) {
+					addHighlight(triggerElement);
+				}
 			}
 		});
 
 		return {
-			menu,
-			trigger,
-			item: m.item,
-			checkboxItem: m.checkboxItem,
-			arrow: m.arrow,
-			createSubmenu: m.createSubMenu,
-			createMenuRadioGroup: m.createMenuRadioGroup,
-			separator: m.separator,
+			elements: {
+				menu,
+				trigger,
+				item: m.item,
+				checkboxItem: m.checkboxItem,
+				arrow: m.arrow,
+				separator: m.separator,
+			},
+			builders: {
+				createSubmenu: m.createSubmenu,
+				createMenuRadioGroup: m.createMenuRadioGroup,
+			},
+			states: {
+				open: rootOpen,
+			},
+			options,
 		};
 	};
 
@@ -493,14 +517,14 @@ export function createMenubar(props?: CreateMenubarProps) {
 
 		// Calculate the index of the next menu item
 		let nextIndex: number;
-		const loop = withDefaults.loop;
+		const $loop = get(loop);
 		switch (e.key) {
 			case kbd.ARROW_RIGHT:
 				nextIndex =
-					currentIndex < candidateNodes.length - 1 ? currentIndex + 1 : loop ? 0 : currentIndex;
+					currentIndex < candidateNodes.length - 1 ? currentIndex + 1 : $loop ? 0 : currentIndex;
 				break;
 			case kbd.ARROW_LEFT:
-				nextIndex = currentIndex > 0 ? currentIndex - 1 : loop ? candidateNodes.length - 1 : 0;
+				nextIndex = currentIndex > 0 ? currentIndex - 1 : $loop ? candidateNodes.length - 1 : 0;
 				break;
 			case kbd.HOME:
 				nextIndex = 0;
@@ -518,7 +542,12 @@ export function createMenubar(props?: CreateMenubarProps) {
 	}
 
 	return {
-		menubar,
-		createMenu,
+		elements: {
+			menubar,
+		},
+		builders: {
+			createMenu,
+		},
+		options,
 	};
 }
diff --git a/src/lib/builders/menubar/types.ts b/src/lib/builders/menubar/types.ts
index 484b14df..85c53bb9 100644
--- a/src/lib/builders/menubar/types.ts
+++ b/src/lib/builders/menubar/types.ts
@@ -1,6 +1,8 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { Menu } from '../menu';
 import type { createMenubar } from './create';
 
+// Props
 export type CreateMenubarProps = {
 	/**
 	 * Whether or not the menubar should loop when
@@ -18,4 +20,24 @@ export type CreateMenuRadioGroupProps = Menu['radioGroup'];
 export type MenubarRadioItemProps = Menu['radioItem'];
 export type MenubarRadioItemActionProps = Menu['radioItemAction'];
 
-export type CreateMenubarReturn = ReturnType<typeof createMenubar>;
+// Returns
+export type Menubar = BuilderReturn<typeof createMenubar>;
+export type MenubarElements = Menubar['elements'];
+export type MenubarOptions = Menubar['options'];
+export type MenubarBuilders = Menubar['builders'];
+
+export type MenubarMenu = BuilderReturn<MenubarBuilders['createMenu']>;
+export type MenubarMenuElements = MenubarMenu['elements'];
+export type MenubarMenuOptions = MenubarMenu['options'];
+export type MenubarMenuStates = MenubarMenu['states'];
+export type MenubarMenuBuilders = MenubarMenu['builders'];
+
+export type MenubarMenuSubmenu = BuilderReturn<MenubarMenuBuilders['createSubmenu']>;
+export type MenubarMenuSubmenuElements = MenubarMenuSubmenu['elements'];
+export type MenubarMenuSubmenuOptions = MenubarMenuSubmenu['options'];
+export type MenubarMenuSubmenuStates = MenubarMenuSubmenu['states'];
+
+export type MenubarMenuRadioGroup = BuilderReturn<MenubarMenuBuilders['createMenuRadioGroup']>;
+export type MenubarMenuRadioGroupElements = MenubarMenuRadioGroup['elements'];
+export type MenubarMenuRadioGroupStates = MenubarMenuRadioGroup['states'];
+export type MenubarMenuRadioGroupHelpers = MenubarMenuRadioGroup['helpers'];
diff --git a/src/lib/builders/pagination/create.ts b/src/lib/builders/pagination/create.ts
index 32742026..7cd33acb 100644
--- a/src/lib/builders/pagination/create.ts
+++ b/src/lib/builders/pagination/create.ts
@@ -5,6 +5,7 @@ import {
 	executeCallbacks,
 	kbd,
 	omit,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
 import { derived, get, writable } from 'svelte/store';
@@ -21,17 +22,20 @@ type PaginationParts = 'page' | 'prev' | 'next';
 const { name, selector } = createElHelpers<PaginationParts>('pagination');
 
 export function createPagination(props: CreatePaginationProps) {
-	const withDefaults = { ...defaults, ...props };
-	const options = writable(omit(withDefaults, 'page'));
+	const withDefaults = { ...defaults, ...props } satisfies CreatePaginationProps;
 	const page = writable(withDefaults.page);
 
-	const totalPages = derived([options], ([$options]) => {
-		return Math.ceil($options.count / $options.perPage);
+	// options
+	const options = toWritableStores(omit(withDefaults, 'page'));
+	const { perPage, siblingCount, count } = options;
+
+	const totalPages = derived([count, perPage], ([$count, $perPage]) => {
+		return Math.ceil($count / $perPage);
 	});
 
-	const range = derived([page, options], ([$page, $options]) => {
-		const start = ($page - 1) * $options.perPage;
-		const end = Math.min(start + $options.perPage, $options.count);
+	const range = derived([page, perPage, count], ([$page, $perPage, $count]) => {
+		const start = ($page - 1) * $perPage;
+		const end = Math.min(start + $perPage, $count);
 		return { start, end };
 	});
 
@@ -41,8 +45,8 @@ export function createPagination(props: CreatePaginationProps) {
 		}),
 	});
 
-	const pages = derived([page, totalPages, options], ([$page, $totalPages, { siblingCount }]) => {
-		return getPageItems({ page: $page, totalPages: $totalPages, siblingCount });
+	const pages = derived([page, totalPages, siblingCount], ([$page, $totalPages, $siblingCount]) => {
+		return getPageItems({ page: $page, totalPages: $totalPages, siblingCount: $siblingCount });
 	});
 
 	const keydown = (e: KeyboardEvent) => {
@@ -146,14 +150,18 @@ export function createPagination(props: CreatePaginationProps) {
 	});
 
 	return {
+		elements: {
+			root,
+			pageTrigger,
+			prevButton,
+			nextButton,
+		},
+		states: {
+			range,
+			page,
+			pages,
+			totalPages,
+		},
 		options,
-		page,
-		pages,
-		range,
-		totalPages,
-		root,
-		pageTrigger,
-		prevButton,
-		nextButton,
 	};
 }
diff --git a/src/lib/builders/pagination/types.ts b/src/lib/builders/pagination/types.ts
index 19f87f00..bc51a8be 100644
--- a/src/lib/builders/pagination/types.ts
+++ b/src/lib/builders/pagination/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createPagination } from './create';
 
 export type CreatePaginationProps = {
@@ -31,4 +32,7 @@ export type GetPageItemsArgs = {
 	page?: number;
 };
 
-export type CreatePaginationReturn = ReturnType<typeof createPagination>;
+export type Pagination = BuilderReturn<typeof createPagination>;
+export type PaginationElements = Pagination['elements'];
+export type PaginationOptions = Pagination['options'];
+export type PaginationStates = Pagination['states'];
diff --git a/src/lib/builders/pin-input/create.ts b/src/lib/builders/pin-input/create.ts
index 1e07bcb9..70327216 100644
--- a/src/lib/builders/pin-input/create.ts
+++ b/src/lib/builders/pin-input/create.ts
@@ -8,6 +8,7 @@ import {
 	omit,
 	prev,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import type { Defaults } from '@melt-ui/svelte/internal/types';
 import { tick } from 'svelte';
@@ -30,13 +31,16 @@ const defaults = {
 	placeholder: '○',
 	disabled: false,
 	type: 'text',
+	name: undefined,
 } satisfies Defaults<CreatePinInputProps>;
 
 export function createPinInput(props?: CreatePinInputProps) {
-	const withDefaults = { ...defaults, ...props };
-	const options = writable(omit(withDefaults, 'value'));
+	const withDefaults = { ...defaults, ...props } satisfies CreatePinInputProps;
 
-	const value = writable((props?.value ?? []) as string[]);
+	const options = toWritableStores(omit(withDefaults, 'value'));
+	const { placeholder, disabled, type, name: nameStore } = options;
+
+	const value = writable((props?.value ?? []) satisfies string[]);
 	const valueStr = derived(value, (v) => v.join(''));
 
 	const root = builder(name(), {
@@ -49,13 +53,13 @@ export function createPinInput(props?: CreatePinInputProps) {
 	});
 
 	const input = builder(name('input'), {
-		stores: [value, options],
-		returned: ([$value, $options]) => {
+		stores: [value, placeholder, disabled, type],
+		returned: ([$value, $placeholder, $disabled, $type]) => {
 			return {
 				'data-complete': $value.length && $value.every((v) => v.length > 0) ? '' : undefined,
-				placeholder: $options.placeholder,
-				disabled: $options.disabled,
-				type: $options.type,
+				placeholder: $placeholder,
+				disabled: $disabled,
+				type: $type,
 			};
 		},
 		action: (node: HTMLInputElement) => {
@@ -173,8 +177,7 @@ export function createPinInput(props?: CreatePinInputProps) {
 					});
 				}),
 				addEventListener(node, 'blur', () => {
-					const $options = get(options);
-					node.placeholder = $options.placeholder;
+					node.placeholder = get(placeholder);
 				})
 			);
 
@@ -187,10 +190,10 @@ export function createPinInput(props?: CreatePinInputProps) {
 	});
 
 	const hiddenInput = builder(name('hidden-input'), {
-		stores: [value, options],
-		returned: ([$value, $options]) => ({
+		stores: [value, nameStore],
+		returned: ([$value, $nameStore]) => ({
 			value: $value,
-			name: $options.name,
+			name: $nameStore,
 			hidden: true,
 			style: styleToString({
 				display: 'none',
@@ -206,12 +209,18 @@ export function createPinInput(props?: CreatePinInputProps) {
 	};
 
 	return {
+		elements: {
+			root,
+			input,
+			hiddenInput,
+		},
+		states: {
+			value,
+			valueStr,
+		},
+		helpers: {
+			clear,
+		},
 		options,
-		clear,
-		value,
-		valueStr,
-		root,
-		input,
-		hiddenInput,
 	};
 }
diff --git a/src/lib/builders/pin-input/types.ts b/src/lib/builders/pin-input/types.ts
index ede198fd..b421b645 100644
--- a/src/lib/builders/pin-input/types.ts
+++ b/src/lib/builders/pin-input/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createPinInput } from './create';
 
 export type CreatePinInputProps = {
@@ -8,4 +9,8 @@ export type CreatePinInputProps = {
 	type?: string;
 };
 
-export type CreatePinInputReturn = ReturnType<typeof createPinInput>;
+export type PinInput = BuilderReturn<typeof createPinInput>;
+export type PinInputElements = PinInput['elements'];
+export type PinInputOptions = PinInput['options'];
+export type PinInputStates = PinInput['states'];
+export type PinInputHelpers = PinInput['helpers'];
diff --git a/src/lib/builders/popover/create.ts b/src/lib/builders/popover/create.ts
index 4e844359..3d986ff6 100644
--- a/src/lib/builders/popover/create.ts
+++ b/src/lib/builders/popover/create.ts
@@ -6,17 +6,18 @@ import {
 	generateId,
 	isBrowser,
 	noop,
-	omit,
 	sleep,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 
 import { usePopper } from '$lib/internal/actions';
 import type { Defaults } from '$lib/internal/types';
 import { overridable } from '$lib/internal/helpers';
 import { tick } from 'svelte';
-import { get, readable, writable } from 'svelte/store';
+import { get, writable } from 'svelte/store';
 import type { CreatePopoverProps } from './types';
+import { omit } from '../../internal/helpers/object';
 
 const defaults = {
 	positioning: {
@@ -24,17 +25,19 @@ const defaults = {
 	},
 	arrowSize: 8,
 	defaultOpen: false,
+	disableFocusTrap: false,
+	onOpenChange: undefined,
 } satisfies Defaults<CreatePopoverProps>;
 
 type PopoverParts = 'trigger' | 'content' | 'arrow' | 'close';
 const { name } = createElHelpers<PopoverParts>('popover');
 
 export function createPopover(args?: CreatePopoverProps) {
-	const withDefaults = { ...defaults, ...args };
-	const options = writable(omit(withDefaults, 'open', 'onOpenChange', 'defaultOpen'));
+	const withDefaults = { ...defaults, ...args } satisfies CreatePopoverProps;
 
-	const positioning = readable(withDefaults.positioning);
-	const arrowSize = readable(withDefaults.arrowSize);
+	// options
+	const options = toWritableStores(omit(withDefaults, 'open'));
+	const { positioning, arrowSize, disableFocusTrap } = options;
 
 	const openWritable = withDefaults.open ?? writable(withDefaults.defaultOpen);
 	const open = overridable(openWritable, withDefaults?.onOpenChange);
@@ -62,8 +65,8 @@ export function createPopover(args?: CreatePopoverProps) {
 			let unsubPopper = noop;
 
 			const unsubDerived = effect(
-				[open, activeTrigger, positioning, options],
-				([$open, $activeTrigger, $positioning, $options]) => {
+				[open, activeTrigger, positioning, disableFocusTrap],
+				([$open, $activeTrigger, $positioning, $disableFocusTrap]) => {
 					unsubPopper();
 					if ($open && $activeTrigger) {
 						tick().then(() => {
@@ -72,7 +75,7 @@ export function createPopover(args?: CreatePopoverProps) {
 								open,
 								options: {
 									floating: $positioning,
-									focusTrap: $options.disableFocusTrap ? null : undefined,
+									focusTrap: $disableFocusTrap ? null : undefined,
 								},
 							});
 
@@ -166,6 +169,16 @@ export function createPopover(args?: CreatePopoverProps) {
 			sleep(1).then(() => $activeTrigger.focus());
 		}
 	});
-
-	return { trigger, open, content, arrow, close, options };
+	return {
+		elements: {
+			trigger,
+			content,
+			arrow,
+			close,
+		},
+		states: {
+			open,
+		},
+		options,
+	};
 }
diff --git a/src/lib/builders/popover/types.ts b/src/lib/builders/popover/types.ts
index 72d465c4..9ba083f9 100644
--- a/src/lib/builders/popover/types.ts
+++ b/src/lib/builders/popover/types.ts
@@ -2,6 +2,7 @@ import type { FloatingConfig } from '$lib/internal/actions';
 import type { ChangeFn } from '$lib/internal/helpers';
 import type { Writable } from 'svelte/store';
 import type { createPopover } from './create';
+import type { BuilderReturn } from '$lib/internal/types';
 
 export type CreatePopoverProps = {
 	positioning?: FloatingConfig;
@@ -19,4 +20,7 @@ export type CreatePopoverProps = {
 	disableFocusTrap?: boolean;
 };
 
-export type CreatePopoverReturn = ReturnType<typeof createPopover>;
+export type Popover = BuilderReturn<typeof createPopover>;
+export type PopoverElements = Popover['elements'];
+export type PopoverOptions = Popover['options'];
+export type PopoverStates = Popover['states'];
diff --git a/src/lib/builders/progress/create.ts b/src/lib/builders/progress/create.ts
index e6ba4cac..5bf4fcb9 100644
--- a/src/lib/builders/progress/create.ts
+++ b/src/lib/builders/progress/create.ts
@@ -1,5 +1,5 @@
 import type { Defaults } from '$lib/internal/types';
-import { builder, createElHelpers } from '@melt-ui/svelte/internal/helpers';
+import { builder, createElHelpers, omit, toWritableStores } from '@melt-ui/svelte/internal/helpers';
 import { writable } from 'svelte/store';
 import type { CreateProgressProps } from './types';
 
@@ -9,10 +9,12 @@ const defaults = {
 
 const { name } = createElHelpers('progress');
 
-export const createProgress = (props: CreateProgressProps = defaults) => {
-	const withDefaults = { ...defaults, ...props };
+export const createProgress = (props?: CreateProgressProps) => {
+	const withDefaults = { ...defaults, ...props } satisfies CreateProgressProps;
+
+	const options = toWritableStores(omit(withDefaults, 'value'));
+	const { max } = options;
 	const value = writable(withDefaults.value ?? null);
-	const max = writable(withDefaults.max);
 
 	const root = builder(name(), {
 		stores: [value, max],
@@ -32,9 +34,12 @@ export const createProgress = (props: CreateProgressProps = defaults) => {
 	});
 
 	return {
-		value,
-		max,
-		root,
-		progress: root,
+		elements: {
+			root,
+		},
+		states: {
+			value,
+		},
+		options,
 	};
 };
diff --git a/src/lib/builders/progress/types.ts b/src/lib/builders/progress/types.ts
index 177cf2a0..99b166a8 100644
--- a/src/lib/builders/progress/types.ts
+++ b/src/lib/builders/progress/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createProgress } from './create';
 
 export type CreateProgressProps = {
@@ -5,4 +6,7 @@ export type CreateProgressProps = {
 	max?: number;
 };
 
-export type CreateProgressReturn = ReturnType<typeof createProgress>;
+export type Progress = BuilderReturn<typeof createProgress>;
+export type ProgressElements = Progress['elements'];
+export type ProgressOptions = Progress['options'];
+export type ProgressStates = Progress['states'];
diff --git a/src/lib/builders/radio-group/create.ts b/src/lib/builders/radio-group/create.ts
index 930b75fb..5dc4952f 100644
--- a/src/lib/builders/radio-group/create.ts
+++ b/src/lib/builders/radio-group/create.ts
@@ -6,6 +6,8 @@ import {
 	getDirectionalKeys,
 	isLeftClick,
 	kbd,
+	omit,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import { getElemDirection } from '$lib/internal/helpers/locale';
 import type { Defaults } from '$lib/internal/types';
@@ -22,41 +24,40 @@ const defaults = {
 type RadioGroupParts = 'item' | 'item-input';
 const { name, selector } = createElHelpers<RadioGroupParts>('radio-group');
 
-export function createRadioGroup(props: CreateRadioGroupProps = {}) {
-	const withDefaults = { ...defaults, ...props };
-	const options = writable({
-		disabled: withDefaults.disabled,
-		required: withDefaults.required,
-		loop: withDefaults.loop,
-		orientation: withDefaults.orientation,
-	});
+export function createRadioGroup(props?: CreateRadioGroupProps) {
+	const withDefaults = { ...defaults, ...props } satisfies CreateRadioGroupProps;
+
+	// options
+	const options = toWritableStores(omit(withDefaults, 'value'));
+	const { disabled, required, loop, orientation } = options;
+
 	const value = writable(withDefaults.value ?? null);
 
 	const root = builder(name(), {
-		stores: options,
-		returned: ($options) => {
+		stores: [required, orientation],
+		returned: ([$required, $orientation]) => {
 			return {
 				role: 'radiogroup',
-				'aria-required': $options.required,
-				'data-orientation': $options.orientation,
+				'aria-required': $required,
+				'data-orientation': $orientation,
 			} as const;
 		},
 	});
 
 	const item = builder(name('item'), {
-		stores: [options, value],
-		returned: ([$options, $value]) => {
+		stores: [value, orientation, disabled],
+		returned: ([$value, $orientation, $disabled]) => {
 			return (props: RadioGroupItemProps) => {
 				const itemValue = typeof props === 'string' ? props : props.value;
 				const argDisabled = typeof props === 'string' ? false : !!props.disabled;
-				const disabled = $options.disabled || (argDisabled as boolean);
+				const disabled = $disabled || (argDisabled as boolean);
 
 				const checked = $value === itemValue;
 
 				return {
 					disabled,
 					'data-value': itemValue,
-					'data-orientation': $options.orientation,
+					'data-orientation': $orientation,
 					'data-disabled': disabled ? true : undefined,
 					'data-state': checked ? 'checked' : 'unchecked',
 					'aria-checked': checked,
@@ -86,7 +87,6 @@ export function createRadioGroup(props: CreateRadioGroupProps = {}) {
 					value.set(itemValue);
 				}),
 				addEventListener(node, 'keydown', (e) => {
-					const $options = get(options);
 					const el = e.currentTarget as HTMLElement;
 					const root = el.closest(selector()) as HTMLElement;
 
@@ -94,13 +94,14 @@ export function createRadioGroup(props: CreateRadioGroupProps = {}) {
 					const currentIndex = items.indexOf(el);
 
 					const dir = getElemDirection(root);
-					const { nextKey, prevKey } = getDirectionalKeys(dir, $options.orientation);
+					const { nextKey, prevKey } = getDirectionalKeys(dir, get(orientation));
+					const $loop = get(loop);
 
 					if (e.key === nextKey) {
 						e.preventDefault();
 						const nextIndex = currentIndex + 1;
 						if (nextIndex >= items.length) {
-							if ($options.loop) {
+							if ($loop) {
 								items[0].focus();
 							}
 						} else {
@@ -110,7 +111,7 @@ export function createRadioGroup(props: CreateRadioGroupProps = {}) {
 						e.preventDefault();
 						const prevIndex = currentIndex - 1;
 						if (prevIndex < 0) {
-							if ($options.loop) {
+							if ($loop) {
 								items[items.length - 1].focus();
 							}
 						} else {
@@ -133,12 +134,12 @@ export function createRadioGroup(props: CreateRadioGroupProps = {}) {
 	});
 
 	const itemInput = builder(name('item-input'), {
-		stores: [options, value],
-		returned: ([$options, $value]) => {
+		stores: [disabled, value],
+		returned: ([$disabled, $value]) => {
 			return (props: RadioGroupItemProps) => {
 				const itemValue = typeof props === 'string' ? props : props.value;
 				const argDisabled = typeof props === 'string' ? false : !!props.disabled;
-				const disabled = $options.disabled || argDisabled;
+				const disabled = $disabled || argDisabled;
 
 				return {
 					type: 'hidden',
@@ -159,11 +160,17 @@ export function createRadioGroup(props: CreateRadioGroupProps = {}) {
 	});
 
 	return {
+		elements: {
+			root,
+			item,
+			itemInput,
+		},
+		states: {
+			value,
+		},
+		helpers: {
+			isChecked,
+		},
 		options,
-		value,
-		isChecked,
-		root,
-		item,
-		itemInput,
 	};
 }
diff --git a/src/lib/builders/radio-group/types.ts b/src/lib/builders/radio-group/types.ts
index 348c0f4e..2a803dca 100644
--- a/src/lib/builders/radio-group/types.ts
+++ b/src/lib/builders/radio-group/types.ts
@@ -1,4 +1,4 @@
-import type { Orientation } from '$lib/internal/types';
+import type { BuilderReturn, Orientation } from '$lib/internal/types';
 import type { createRadioGroup } from './create';
 
 export type CreateRadioGroupProps = {
@@ -16,4 +16,8 @@ export type RadioGroupItemProps =
 	  }
 	| string;
 
-export type CreateRadioGroupReturn = ReturnType<typeof createRadioGroup>;
+export type RadioGroup = BuilderReturn<typeof createRadioGroup>;
+export type RadioGroupElements = RadioGroup['elements'];
+export type RadioGroupOptions = RadioGroup['options'];
+export type RadioGroupStates = RadioGroup['states'];
+export type RadioGroupHelpers = RadioGroup['helpers'];
diff --git a/src/lib/builders/select/create.ts b/src/lib/builders/select/create.ts
index 12ed54b2..29e5657e 100644
--- a/src/lib/builders/select/create.ts
+++ b/src/lib/builders/select/create.ts
@@ -25,6 +25,7 @@ import {
 	prev,
 	removeScroll,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import { getFirstOption, getOptions } from '$lib/internal/helpers/list';
 import { sleep } from '$lib/internal/helpers/sleep';
@@ -44,14 +45,25 @@ const defaults = {
 	},
 	preventScroll: true,
 	loop: false,
+	name: undefined,
 } satisfies Defaults<CreateSelectProps>;
 
 type SelectParts = 'menu' | 'trigger' | 'option' | 'group' | 'group-label' | 'arrow' | 'input';
 const { name } = createElHelpers<SelectParts>('select');
 
 export function createSelect(props?: CreateSelectProps) {
-	const withDefaults = { ...defaults, ...props } as CreateSelectProps;
-	const options = writable(omit(withDefaults, 'value', 'label'));
+	const withDefaults = { ...defaults, ...props } satisfies CreateSelectProps;
+
+	const options = toWritableStores(omit(withDefaults, 'value', 'label'));
+	const {
+		positioning,
+		arrowSize,
+		required,
+		disabled,
+		loop,
+		preventScroll,
+		name: nameStore,
+	} = options;
 
 	const open = writable(false);
 	const value = writable<unknown>(withDefaults.value ?? null);
@@ -110,8 +122,8 @@ export function createSelect(props?: CreateSelectProps) {
 			let unsubPopper = noop;
 
 			const unsubDerived = effect(
-				[open, activeTrigger, options],
-				([$open, $activeTrigger, $options]) => {
+				[open, activeTrigger, positioning],
+				([$open, $activeTrigger, $positioning]) => {
 					unsubPopper();
 					if ($open && $activeTrigger) {
 						tick().then(() => {
@@ -119,7 +131,7 @@ export function createSelect(props?: CreateSelectProps) {
 								anchorElement: $activeTrigger,
 								open,
 								options: {
-									floating: $options.positioning,
+									floating: $positioning,
 								},
 							});
 
@@ -178,18 +190,17 @@ export function createSelect(props?: CreateSelectProps) {
 	});
 
 	const trigger = builder(name('trigger'), {
-		stores: [open, options],
-
-		returned: ([$open, $options]) => {
+		stores: [open, disabled, required],
+		returned: ([$open, $disabled, $required]) => {
 			return {
 				role: 'combobox',
 				'aria-autocomplete': 'none',
 				'aria-controls': ids.menu,
 				'aria-expanded': $open,
-				'aria-required': $options.required,
+				'aria-required': $required,
 				'data-state': $open ? 'open' : 'closed',
-				'data-disabled': $options.disabled ? true : undefined,
-				disabled: $options.disabled,
+				'data-disabled': $disabled ? true : undefined,
+				disabled: $disabled,
 				id: ids.trigger,
 				tabindex: 0,
 			} as const;
@@ -197,8 +208,7 @@ export function createSelect(props?: CreateSelectProps) {
 		action: (node: HTMLElement) => {
 			const unsub = executeCallbacks(
 				addEventListener(node, 'click', (e) => {
-					const $options = get(options);
-					if ($options.disabled) {
+					if (get(disabled)) {
 						e.preventDefault();
 						return;
 					}
@@ -279,7 +289,9 @@ export function createSelect(props?: CreateSelectProps) {
 		},
 	});
 
-	const { root: separator } = createSeparator({
+	const {
+		elements: { root: separator },
+	} = createSeparator({
 		decorative: true,
 	});
 
@@ -301,13 +313,13 @@ export function createSelect(props?: CreateSelectProps) {
 	});
 
 	const arrow = builder(name('arrow'), {
-		stores: options,
-		returned: ($options) => ({
+		stores: arrowSize,
+		returned: ($arrowSize) => ({
 			'data-arrow': true,
 			style: styleToString({
 				position: 'absolute',
-				width: `var(--arrow-size, ${$options.arrowSize}px)`,
-				height: `var(--arrow-size, ${$options.arrowSize}px)`,
+				width: `var(--arrow-size, ${$arrowSize}px)`,
+				height: `var(--arrow-size, ${$arrowSize}px)`,
 			}),
 		}),
 	});
@@ -427,8 +439,7 @@ export function createSelect(props?: CreateSelectProps) {
 		const unsubs: Array<() => void> = [];
 
 		if (!isBrowser) return;
-		const $options = get(options);
-		if ($open && $options.preventScroll) {
+		if ($open && get(preventScroll)) {
 			unsubs.push(removeScroll());
 		}
 
@@ -493,17 +504,17 @@ export function createSelect(props?: CreateSelectProps) {
 	});
 
 	const input = builder(name('input'), {
-		stores: [value, options],
-		returned: ([$value, $options]) => {
+		stores: [value, required, disabled, nameStore],
+		returned: ([$value, $required, $disabled, $nameStore]) => {
 			return {
 				type: 'hidden',
-				name: $options.name,
+				name: $nameStore,
 				value: $value,
 				'aria-hidden': true,
 				hidden: true,
 				tabIndex: -1,
-				required: $options.required,
-				disabled: $options.disabled,
+				required: $required,
+				disabled: $disabled,
 				style: styleToString({
 					position: 'absolute',
 					opacity: 0,
@@ -560,20 +571,19 @@ export function createSelect(props?: CreateSelectProps) {
 		const currentIndex = candidateNodes.indexOf(currentFocusedItem);
 		// Find the next menu item to highlight.
 		let nextItem: HTMLElement;
-		const $options = get(options);
-		const loop = $options.loop;
+		const $loop = get(loop);
 		switch (e.key) {
 			case kbd.ARROW_DOWN:
-				nextItem = next(candidateNodes, currentIndex, loop);
+				nextItem = next(candidateNodes, currentIndex, $loop);
 				break;
 			case kbd.PAGE_DOWN:
-				nextItem = forward(candidateNodes, currentIndex, 10, loop);
+				nextItem = forward(candidateNodes, currentIndex, 10, $loop);
 				break;
 			case kbd.ARROW_UP:
-				nextItem = prev(candidateNodes, currentIndex, loop);
+				nextItem = prev(candidateNodes, currentIndex, $loop);
 				break;
 			case kbd.PAGE_UP:
-				nextItem = back(candidateNodes, currentIndex, 10, loop);
+				nextItem = back(candidateNodes, currentIndex, 10, $loop);
 				break;
 			case kbd.HOME:
 				nextItem = candidateNodes[0];
@@ -606,18 +616,24 @@ export function createSelect(props?: CreateSelectProps) {
 	}
 
 	return {
+		elements: {
+			menu,
+			trigger,
+			option,
+			input,
+			group,
+			groupLabel,
+			arrow,
+			separator,
+		},
+		states: {
+			open,
+			value,
+			label,
+		},
+		helpers: {
+			isSelected,
+		},
 		options,
-		open,
-		isSelected,
-		value,
-		trigger,
-		menu,
-		option,
-		input,
-		label,
-		separator,
-		group,
-		groupLabel,
-		arrow,
 	};
 }
diff --git a/src/lib/builders/select/types.ts b/src/lib/builders/select/types.ts
index b2f5428b..95cbf61b 100644
--- a/src/lib/builders/select/types.ts
+++ b/src/lib/builders/select/types.ts
@@ -1,4 +1,5 @@
 import type { FloatingConfig } from '$lib/internal/actions';
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createSelect } from './create';
 
 export type CreateSelectProps = {
@@ -19,4 +20,8 @@ export type SelectOptionProps = {
 	disabled?: boolean;
 };
 
-export type CreateSelectReturn = ReturnType<typeof createSelect>;
+export type Select = BuilderReturn<typeof createSelect>;
+export type SelectElements = Select['elements'];
+export type SelectOptions = Select['options'];
+export type SelectStates = Select['states'];
+export type SelectHelpers = Select['helpers'];
diff --git a/src/lib/builders/separator/create.ts b/src/lib/builders/separator/create.ts
index 81712583..080fe720 100644
--- a/src/lib/builders/separator/create.ts
+++ b/src/lib/builders/separator/create.ts
@@ -1,6 +1,5 @@
-import { builder } from '$lib/internal/helpers';
+import { builder, toWritableStores } from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
-import { writable } from 'svelte/store';
 import type { CreateSeparatorProps } from './types';
 
 const defaults = {
@@ -8,25 +7,28 @@ const defaults = {
 	decorative: false,
 } satisfies Defaults<CreateSeparatorProps>;
 
-export const createSeparator = (props: CreateSeparatorProps = defaults) => {
-	const withDefaults = { ...defaults, ...props };
-	const options = writable({ ...withDefaults });
+export const createSeparator = (props?: CreateSeparatorProps) => {
+	const withDefaults = { ...defaults, ...props } satisfies CreateSeparatorProps;
+	const options = toWritableStores(withDefaults);
+	const { orientation, decorative } = options;
 
 	const root = builder('separator', {
-		stores: options,
-		returned: ({ orientation, decorative }) => {
-			const ariaOrientation = orientation === 'vertical' ? orientation : undefined;
+		stores: [orientation, decorative],
+		returned: ([$orientation, $decorative]) => {
+			const ariaOrientation = $orientation === 'vertical' ? $orientation : undefined;
 			return {
-				role: decorative ? 'none' : 'separator',
+				role: $decorative ? 'none' : 'separator',
 				'aria-orientation': ariaOrientation,
-				'aria-hidden': decorative,
-				'data-orientation': orientation,
+				'aria-hidden': $decorative,
+				'data-orientation': $orientation,
 			};
 		},
 	});
 
 	return {
-		root,
+		elements: {
+			root,
+		},
 		options,
 	};
 };
diff --git a/src/lib/builders/separator/types.ts b/src/lib/builders/separator/types.ts
index 2174cf95..5ea0f792 100644
--- a/src/lib/builders/separator/types.ts
+++ b/src/lib/builders/separator/types.ts
@@ -1,4 +1,4 @@
-import type { Orientation } from '$lib/internal/types';
+import type { BuilderReturn, Orientation } from '$lib/internal/types';
 import type { createSeparator } from './create';
 
 export type CreateSeparatorProps = {
@@ -19,4 +19,6 @@ export type CreateSeparatorProps = {
 	decorative?: boolean;
 };
 
-export type CreateSeparatorReturn = ReturnType<typeof createSeparator>;
+export type Separator = BuilderReturn<typeof createSeparator>;
+export type SeparatorElements = Separator['elements'];
+export type SeparatorOptions = Separator['options'];
diff --git a/src/lib/builders/slider/create.ts b/src/lib/builders/slider/create.ts
index 997bebc4..0e85c840 100644
--- a/src/lib/builders/slider/create.ts
+++ b/src/lib/builders/slider/create.ts
@@ -9,6 +9,7 @@ import {
 	kbd,
 	omit,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import { derived, get, writable } from 'svelte/store';
 import type { CreateSliderProps } from './types';
@@ -24,9 +25,11 @@ const defaults = {
 
 const { name } = createElHelpers('slider');
 
-export const createSlider = (props: CreateSliderProps = defaults) => {
-	const withDefaults = { ...defaults, ...props };
-	const options = writable(omit(withDefaults, 'value'));
+export const createSlider = (props?: CreateSliderProps) => {
+	const withDefaults = { ...defaults, ...props } satisfies CreateSliderProps;
+
+	const options = toWritableStores(omit(withDefaults, 'value'));
+	const { min, max, step, orientation, disabled } = options;
 
 	const value = writable(withDefaults.value);
 
@@ -39,33 +42,33 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 	};
 
 	const root = builder(name(), {
-		stores: options,
-		returned: ($options) => {
+		stores: [disabled, orientation],
+		returned: ([$disabled, $orientation]) => {
 			return {
-				disabled: $options.disabled,
-				'data-orientation': $options.orientation,
-				style: $options.disabled ? undefined : 'touch-action: none;',
+				disabled: $disabled,
+				'data-orientation': $orientation,
+				style: $disabled ? undefined : 'touch-action: none;',
 				'data-melt-id': ids.root,
 			};
 		},
 	});
 
-	const position = derived(options, ($options) => {
+	const position = derived([min, max], ([$min, $max]) => {
 		return (val: number) => {
-			const pos = ((val - $options.min) / ($options.max - $options.min)) * 100;
+			const pos = ((val - $min) / ($max - $min)) * 100;
 
 			return Math.abs(pos);
 		};
 	});
 
 	const range = builder(name('range'), {
-		stores: [value, options, position],
-		returned: ([$value, $options, $position]) => {
+		stores: [value, orientation, position],
+		returned: ([$value, $orientation, $position]) => {
 			const minimum = $value.length > 1 ? $position(Math.min(...$value) ?? 0) : 0;
 			const maximum = 100 - $position(Math.max(...$value) ?? 0);
 
 			const orientationStyles =
-				$options.orientation === 'horizontal'
+				$orientation === 'horizontal'
 					? { left: `${minimum}%`, right: `${maximum}%` }
 					: { top: `${maximum}%`, bottom: `${minimum}%` };
 
@@ -108,13 +111,12 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 	};
 
 	const thumb = builder(name('thumb'), {
-		stores: [value, options, position],
-		returned: ([$value, $options, $position]) => {
+		stores: [value, position, min, max, disabled, orientation],
+		returned: ([$value, $position, $min, $max, $disabled, $orientation]) => {
 			let index = -1;
 
 			return () => {
 				index++;
-				const { min: $min, max: $max, disabled: $disabled } = $options;
 
 				const currentThumb = get(currentThumbIndex);
 
@@ -132,7 +134,7 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 					'data-melt-part': 'thumb',
 					style: styleToString({
 						position: 'absolute',
-						...($options.orientation === 'horizontal'
+						...($orientation === 'horizontal'
 							? { left: thumbPosition, translate: '-50% 0' }
 							: { bottom: thumbPosition, translate: '0 50%' }),
 					}),
@@ -142,10 +144,9 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 		},
 		action: (node: HTMLElement) => {
 			const unsub = addEventListener(node, 'keydown', (event) => {
-				const $options = get(options);
-				const $min = $options.min;
-				const $max = $options.max;
-				if ($options.disabled) return;
+				const $min = get(min);
+				const $max = get(max);
+				if (get(disabled)) return;
 
 				const target = event.currentTarget as HTMLElement;
 				const thumbs = getAllThumbs();
@@ -169,8 +170,9 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 
 				event.preventDefault();
 
-				const step = $options.step;
+				const $step = get(step);
 				const $value = get(value);
+				const $orientation = get(orientation);
 
 				switch (event.key) {
 					case kbd.HOME: {
@@ -182,23 +184,23 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 						break;
 					}
 					case kbd.ARROW_LEFT: {
-						if ($options.orientation !== 'horizontal') break;
+						if ($orientation !== 'horizontal') break;
 
 						if (event.metaKey) {
 							updatePosition($min, index);
 						} else if ($value[index] > $min) {
-							const newValue = $value[index] - step;
+							const newValue = $value[index] - $step;
 							updatePosition(newValue, index);
 						}
 						break;
 					}
 					case kbd.ARROW_RIGHT: {
-						if ($options.orientation !== 'horizontal') break;
+						if ($orientation !== 'horizontal') break;
 
 						if (event.metaKey) {
 							updatePosition($max, index);
 						} else if ($value[index] < $max) {
-							const newValue = $value[index] + step;
+							const newValue = $value[index] + $step;
 							updatePosition(newValue, index);
 						}
 						break;
@@ -206,11 +208,11 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 					case kbd.ARROW_UP: {
 						if (event.metaKey) {
 							updatePosition($max, index);
-						} else if ($value[index] > $min && $options.orientation === 'vertical') {
-							const newValue = $value[index] - step;
+						} else if ($value[index] > $min && $orientation === 'vertical') {
+							const newValue = $value[index] - $step;
 							updatePosition(newValue, index);
 						} else if ($value[index] < $max) {
-							const newValue = $value[index] + step;
+							const newValue = $value[index] + $step;
 							updatePosition(newValue, index);
 						}
 						break;
@@ -218,11 +220,11 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 					case kbd.ARROW_DOWN: {
 						if (event.metaKey) {
 							updatePosition($min, index);
-						} else if ($value[index] < $max && $options.orientation === 'vertical') {
-							const newValue = $value[index] + step;
+						} else if ($value[index] < $max && $orientation === 'vertical') {
+							const newValue = $value[index] + $step;
 							updatePosition(newValue, index);
 						} else if ($value[index] > $min) {
-							const newValue = $value[index] - step;
+							const newValue = $value[index] - $step;
 							updatePosition(newValue, index);
 						}
 						break;
@@ -236,111 +238,116 @@ export const createSlider = (props: CreateSliderProps = defaults) => {
 		},
 	});
 
-	effect([root, options], ([$root, $options]) => {
-		const { min: $min, max: $max, disabled: $disabled } = $options;
-		if (!isBrowser || $disabled) return;
-
-		const applyPosition = (
-			clientXY: number,
-			activeThumbIdx: number,
-			leftOrBottom: number,
-			rightOrTop: number
-		) => {
-			const percent = (clientXY - leftOrBottom) / (rightOrTop - leftOrBottom);
-			const val = Math.round(percent * ($max - $min) + $min);
-
-			if (val < $min || val > $max) return;
-			const step = $options.step;
-			const newValue = Math.round(val / step) * step;
-
-			updatePosition(newValue, activeThumbIdx);
-		};
-
-		const getClosestThumb = (e: PointerEvent) => {
-			const thumbs = getAllThumbs();
-			if (!thumbs) return;
-			thumbs.forEach((thumb) => thumb?.blur());
+	effect(
+		[root, min, max, disabled, orientation, step],
+		([$root, $min, $max, $disabled, $orientation, $step]) => {
+			if (!isBrowser || $disabled) return;
+
+			const applyPosition = (
+				clientXY: number,
+				activeThumbIdx: number,
+				leftOrBottom: number,
+				rightOrTop: number
+			) => {
+				const percent = (clientXY - leftOrBottom) / (rightOrTop - leftOrBottom);
+				const val = Math.round(percent * ($max - $min) + $min);
+
+				if (val < $min || val > $max) return;
+				const step = $step;
+				const newValue = Math.round(val / step) * step;
+
+				updatePosition(newValue, activeThumbIdx);
+			};
 
-			const distances = thumbs.map((thumb) => {
-				if ($options.orientation === 'horizontal') {
-					const { left, right } = thumb.getBoundingClientRect();
-					return Math.abs(e.clientX - (left + right) / 2);
-				} else {
-					const { top, bottom } = thumb.getBoundingClientRect();
-					return Math.abs(e.clientY - (top + bottom) / 2);
-				}
-			});
+			const getClosestThumb = (e: PointerEvent) => {
+				const thumbs = getAllThumbs();
+				if (!thumbs) return;
+				thumbs.forEach((thumb) => thumb?.blur());
+
+				const distances = thumbs.map((thumb) => {
+					if ($orientation === 'horizontal') {
+						const { left, right } = thumb.getBoundingClientRect();
+						return Math.abs(e.clientX - (left + right) / 2);
+					} else {
+						const { top, bottom } = thumb.getBoundingClientRect();
+						return Math.abs(e.clientY - (top + bottom) / 2);
+					}
+				});
 
-			const thumb = thumbs[distances.indexOf(Math.min(...distances))];
-			const index = thumbs.indexOf(thumb);
+				const thumb = thumbs[distances.indexOf(Math.min(...distances))];
+				const index = thumbs.indexOf(thumb);
 
-			return { thumb, index };
-		};
+				return { thumb, index };
+			};
 
-		const pointerDown = (e: PointerEvent) => {
-			if (e.button !== 0) return;
+			const pointerDown = (e: PointerEvent) => {
+				if (e.button !== 0) return;
 
-			const sliderEl = getElementByMeltId($root['data-melt-id']) as HTMLElement;
-			const closestThumb = getClosestThumb(e);
-			if (!closestThumb || !sliderEl) return;
+				const sliderEl = getElementByMeltId($root['data-melt-id']) as HTMLElement;
+				const closestThumb = getClosestThumb(e);
+				if (!closestThumb || !sliderEl) return;
 
-			if (!sliderEl.contains(e.target as HTMLElement)) return;
-			e.preventDefault();
+				if (!sliderEl.contains(e.target as HTMLElement)) return;
+				e.preventDefault();
 
-			activeThumb.set(closestThumb);
-			closestThumb.thumb.focus();
-			isActive.set(true);
+				activeThumb.set(closestThumb);
+				closestThumb.thumb.focus();
+				isActive.set(true);
 
-			if ($options.orientation === 'horizontal') {
-				const { left, right } = sliderEl.getBoundingClientRect();
-				applyPosition(e.clientX, closestThumb.index, left, right);
-			} else {
-				const { top, bottom } = sliderEl.getBoundingClientRect();
-				applyPosition(e.clientY, closestThumb.index, bottom, top);
-			}
-		};
+				if ($orientation === 'horizontal') {
+					const { left, right } = sliderEl.getBoundingClientRect();
+					applyPosition(e.clientX, closestThumb.index, left, right);
+				} else {
+					const { top, bottom } = sliderEl.getBoundingClientRect();
+					applyPosition(e.clientY, closestThumb.index, bottom, top);
+				}
+			};
 
-		const pointerUp = () => {
-			isActive.set(false);
-		};
+			const pointerUp = () => {
+				isActive.set(false);
+			};
 
-		const pointerMove = (e: PointerEvent) => {
-			if (!get(isActive)) return;
+			const pointerMove = (e: PointerEvent) => {
+				if (!get(isActive)) return;
 
-			const sliderEl = getElementByMeltId($root['data-melt-id']) as HTMLElement;
-			const closestThumb = get(activeThumb);
-			if (!sliderEl || !closestThumb) return;
+				const sliderEl = getElementByMeltId($root['data-melt-id']) as HTMLElement;
+				const closestThumb = get(activeThumb);
+				if (!sliderEl || !closestThumb) return;
 
-			closestThumb.thumb.focus();
+				closestThumb.thumb.focus();
 
-			if ($options.orientation === 'horizontal') {
-				const { left, right } = sliderEl.getBoundingClientRect();
-				applyPosition(e.clientX, closestThumb.index, left, right);
-			} else {
-				const { top, bottom } = sliderEl.getBoundingClientRect();
-				applyPosition(e.clientY, closestThumb.index, bottom, top);
-			}
-		};
+				if ($orientation === 'horizontal') {
+					const { left, right } = sliderEl.getBoundingClientRect();
+					applyPosition(e.clientX, closestThumb.index, left, right);
+				} else {
+					const { top, bottom } = sliderEl.getBoundingClientRect();
+					applyPosition(e.clientY, closestThumb.index, top, bottom);
+				}
+			};
 
-		document.addEventListener('pointerdown', pointerDown);
-		document.addEventListener('pointerup', pointerUp);
-		document.addEventListener('pointerleave', pointerUp);
-		document.addEventListener('pointermove', pointerMove);
+			document.addEventListener('pointerdown', pointerDown);
+			document.addEventListener('pointerup', pointerUp);
+			document.addEventListener('pointerleave', pointerUp);
+			document.addEventListener('pointermove', pointerMove);
 
-		return () => {
-			document.removeEventListener('pointerdown', pointerDown);
-			document.removeEventListener('pointerup', pointerUp);
-			document.removeEventListener('pointerleave', pointerUp);
-			document.removeEventListener('pointermove', pointerMove);
-		};
-	});
+			return () => {
+				document.removeEventListener('pointerdown', pointerDown);
+				document.removeEventListener('pointerup', pointerUp);
+				document.removeEventListener('pointerleave', pointerUp);
+				document.removeEventListener('pointermove', pointerMove);
+			};
+		}
+	);
 
 	return {
+		elements: {
+			root,
+			thumb,
+			range,
+		},
+		states: {
+			value,
+		},
 		options,
-		value,
-		root,
-		slider: root,
-		range,
-		thumb,
 	};
 };
diff --git a/src/lib/builders/slider/types.ts b/src/lib/builders/slider/types.ts
index 82be4b49..0681660f 100644
--- a/src/lib/builders/slider/types.ts
+++ b/src/lib/builders/slider/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createSlider } from './create';
 
 export type CreateSliderProps = {
@@ -9,4 +10,7 @@ export type CreateSliderProps = {
 	disabled?: boolean;
 };
 
-export type CreateSliderReturn = ReturnType<typeof createSlider>;
+export type Slider = BuilderReturn<typeof createSlider>;
+export type SliderElements = Slider['elements'];
+export type SliderOptions = Slider['options'];
+export type SliderStates = Slider['states'];
diff --git a/src/lib/builders/switch/create.ts b/src/lib/builders/switch/create.ts
index 5bce9f6a..4ba1a5c7 100644
--- a/src/lib/builders/switch/create.ts
+++ b/src/lib/builders/switch/create.ts
@@ -1,4 +1,11 @@
-import { addEventListener, builder, createElHelpers, styleToString } from '$lib/internal/helpers';
+import {
+	addEventListener,
+	builder,
+	createElHelpers,
+	omit,
+	styleToString,
+	toWritableStores,
+} from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
 import { derived, get, writable } from 'svelte/store';
 import type { CreateSwitchProps } from './types';
@@ -13,33 +20,30 @@ const defaults = {
 
 const { name } = createElHelpers('switch');
 
-export function createSwitch(props: CreateSwitchProps = {}) {
-	const propsWithDefaults = { ...defaults, ...props };
-	const options = writable({
-		disabled: propsWithDefaults.disabled,
-		required: propsWithDefaults.required,
-		name: propsWithDefaults.name,
-		value: propsWithDefaults.value,
-	});
+export function createSwitch(props?: CreateSwitchProps) {
+	const propsWithDefaults = { ...defaults, ...props } satisfies CreateSwitchProps;
+
+	const options = toWritableStores(omit(propsWithDefaults, 'checked'));
+	const { disabled, required, name: nameStore, value } = options;
+
 	const checked = writable(propsWithDefaults.checked);
 
 	const root = builder(name(), {
-		stores: [checked, options],
-		returned: ([$checked, $options]) => {
+		stores: [checked, disabled, required],
+		returned: ([$checked, $disabled, $required]) => {
 			return {
-				'data-disabled': $options.disabled,
-				disabled: $options.disabled,
+				'data-disabled': $disabled,
+				disabled: $disabled,
 				'data-state': $checked ? 'checked' : 'unchecked',
 				type: 'button',
 				role: 'switch',
 				'aria-checked': $checked,
-				'aria-required': $options.required,
+				'aria-required': $required,
 			} as const;
 		},
 		action(node: HTMLElement) {
 			const unsub = addEventListener(node, 'click', () => {
-				const $options = get(options);
-				if ($options.disabled) return;
+				if (get(disabled)) return;
 
 				checked.update((value) => !value);
 			});
@@ -51,18 +55,18 @@ export function createSwitch(props: CreateSwitchProps = {}) {
 	});
 
 	const input = builder(name('input'), {
-		stores: [checked, options],
-		returned: ([$checked, $options]) => {
+		stores: [checked, nameStore, required, disabled, value],
+		returned: ([$checked, $name, $required, $disabled, $value]) => {
 			return {
 				type: 'checkbox' as const,
 				'aria-hidden': true,
 				hidden: true,
 				tabindex: -1,
-				name: $options.name,
-				value: $options.value,
+				name: $name,
+				value: $value,
 				checked: $checked,
-				required: $options.required,
-				disabled: $options.disabled,
+				required: $required,
+				disabled: $disabled,
 				style: styleToString({
 					position: 'absolute',
 					opacity: 0,
@@ -77,10 +81,16 @@ export function createSwitch(props: CreateSwitchProps = {}) {
 	const isChecked = derived(checked, ($checked) => $checked === true);
 
 	return {
-		root,
-		input,
-		checked,
-		isChecked,
+		elements: {
+			root,
+			input,
+		},
+		states: {
+			checked,
+		},
+		helpers: {
+			isChecked,
+		},
 		options,
 	};
 }
diff --git a/src/lib/builders/switch/types.ts b/src/lib/builders/switch/types.ts
index 714e2182..d4240295 100644
--- a/src/lib/builders/switch/types.ts
+++ b/src/lib/builders/switch/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createSwitch } from './create';
 
 export type CreateSwitchProps = {
@@ -13,4 +14,8 @@ export type CreateSwitchProps = {
 	value?: string;
 };
 
-export type CreateSwitchReturn = ReturnType<typeof createSwitch>;
+export type Switch = BuilderReturn<typeof createSwitch>;
+export type SwitchElements = Switch['elements'];
+export type SwitchOptions = Switch['options'];
+export type SwitchStates = Switch['states'];
+export type SwitchHelpers = Switch['helpers'];
diff --git a/src/lib/builders/tabs/create.ts b/src/lib/builders/tabs/create.ts
index 9750a740..1f57a2d2 100644
--- a/src/lib/builders/tabs/create.ts
+++ b/src/lib/builders/tabs/create.ts
@@ -8,53 +8,58 @@ import {
 	kbd,
 	last,
 	next,
-	omit,
 	prev,
 	isLeftClick,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import { getElemDirection } from '$lib/internal/helpers/locale';
 import type { Defaults } from '$lib/internal/types';
 import { get, writable } from 'svelte/store';
 import type { CreateTabsProps, TabsTriggerProps } from './types';
+import { omit } from '../../internal/helpers/object';
 
 const defaults = {
 	orientation: 'horizontal',
 	activateOnFocus: true,
 	loop: true,
 	autoSet: true,
+	onChange: undefined,
 } satisfies Defaults<CreateTabsProps>;
 
 type TabsParts = 'list' | 'trigger' | 'content';
 const { name, selector } = createElHelpers<TabsParts>('tabs');
 
 export function createTabs(props?: CreateTabsProps) {
-	const withDefaults = { ...defaults, ...props };
-	const options = writable(omit(withDefaults, 'value'));
+	const withDefaults = { ...defaults, ...props } satisfies CreateTabsProps;
+
+	const options = toWritableStores(omit(withDefaults, 'value'));
+	const { orientation, activateOnFocus, loop, autoSet, onChange } = options;
 
 	const value = writable(withDefaults.value);
+
 	let ssrValue = withDefaults.value;
 	value.subscribe((value) => {
-		withDefaults.onChange?.(value);
+		get(onChange)?.(value);
 	});
 
 	// Root
 	const root = builder(name(), {
-		stores: options,
-		returned: ($options) => {
+		stores: orientation,
+		returned: ($orientation) => {
 			return {
-				'data-orientation': $options.orientation,
+				'data-orientation': $orientation,
 			};
 		},
 	});
 
 	// List
 	const list = builder(name('list'), {
-		stores: options,
-		returned: ($options) => {
+		stores: orientation,
+		returned: ($orientation) => {
 			return {
 				role: 'tablist',
-				'aria-orientation': $options.orientation,
-				'data-orientation': $options.orientation,
+				'aria-orientation': $orientation,
+				'data-orientation': $orientation,
 			};
 		},
 	});
@@ -68,12 +73,12 @@ export function createTabs(props?: CreateTabsProps) {
 	};
 
 	const trigger = builder(name('trigger'), {
-		stores: [value, options],
-		returned: ([$value, $options]) => {
+		stores: [value, autoSet, orientation],
+		returned: ([$value, $autoSet, $orientation]) => {
 			return (props: TabsTriggerProps) => {
 				const { value: tabValue, disabled } = parseTriggerProps(props);
 
-				if (!$value && !ssrValue && $options.autoSet) {
+				if (!$value && !ssrValue && $autoSet) {
 					ssrValue = tabValue;
 					value.set(tabValue);
 				}
@@ -90,7 +95,7 @@ export function createTabs(props?: CreateTabsProps) {
 						: 'inactive',
 					tabindex: $value === tabValue ? 0 : -1,
 					'data-value': tabValue,
-					'data-orientation': $options.orientation,
+					'data-orientation': $orientation,
 					'data-disabled': disabled ? true : undefined,
 					disabled,
 				};
@@ -102,11 +107,10 @@ export function createTabs(props?: CreateTabsProps) {
 					node.focus();
 				}),
 				addEventListener(node, 'focus', () => {
-					const $options = get(options);
 					const disabled = node.dataset.disabled === 'true';
 					const tabValue = node.dataset.value;
 
-					if ($options.activateOnFocus && !disabled && tabValue !== undefined) {
+					if (get(activateOnFocus) && !disabled && tabValue !== undefined) {
 						value.set(tabValue);
 					}
 				}),
@@ -135,21 +139,21 @@ export function createTabs(props?: CreateTabsProps) {
 
 					if (!rootEl || !tabValue) return;
 
-					const $options = get(options);
+					const $loop = get(loop);
 
 					const triggers = Array.from(rootEl.querySelectorAll('[role="tab"]')) as HTMLElement[];
 					const enabledTriggers = triggers.filter((el) => !el.hasAttribute('data-disabled'));
 					const triggerIdx = Array.from(enabledTriggers ?? []).findIndex((el) => el === e.target);
 
 					const dir = getElemDirection(rootEl);
-					const { nextKey, prevKey } = getDirectionalKeys(dir, $options.orientation);
+					const { nextKey, prevKey } = getDirectionalKeys(dir, get(orientation));
 
 					if (e.key === nextKey) {
 						e.preventDefault();
-						next(enabledTriggers, triggerIdx, $options.loop).focus();
+						next(enabledTriggers, triggerIdx, $loop).focus();
 					} else if (e.key === prevKey) {
 						e.preventDefault();
-						prev(enabledTriggers, triggerIdx, $options.loop).focus();
+						prev(enabledTriggers, triggerIdx, $loop).focus();
 					} else if (e.key === kbd.ENTER || e.key === kbd.SPACE) {
 						e.preventDefault();
 						value.set(tabValue);
@@ -192,11 +196,15 @@ export function createTabs(props?: CreateTabsProps) {
 	});
 
 	return {
-		value,
+		elements: {
+			root,
+			list,
+			trigger,
+			content,
+		},
+		states: {
+			value,
+		},
 		options,
-		root: root,
-		list: list,
-		trigger: trigger,
-		content: content,
 	};
 }
diff --git a/src/lib/builders/tabs/types.ts b/src/lib/builders/tabs/types.ts
index dee9118d..69afd184 100644
--- a/src/lib/builders/tabs/types.ts
+++ b/src/lib/builders/tabs/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createTabs } from './create';
 
 export type CreateTabsProps = {
@@ -17,4 +18,7 @@ export type TabsTriggerProps =
 	  }
 	| string;
 
-export type CreateTabsReturn = ReturnType<typeof createTabs>;
+export type Tabs = BuilderReturn<typeof createTabs>;
+export type TabsElements = Tabs['elements'];
+export type TabsOptions = Tabs['options'];
+export type TabsStates = Tabs['states'];
diff --git a/src/lib/builders/tags-input/create.ts b/src/lib/builders/tags-input/create.ts
index cc725276..f1516167 100644
--- a/src/lib/builders/tags-input/create.ts
+++ b/src/lib/builders/tags-input/create.ts
@@ -10,6 +10,7 @@ import {
 	kbd,
 	omit,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
 import { derived, get, writable } from 'svelte/store';
@@ -25,15 +26,19 @@ const defaults = {
 	unique: false,
 	blur: 'nothing',
 	addOnPaste: false,
+	maxTags: undefined,
 	allowed: [],
 	denied: [],
+	add: undefined,
+	remove: undefined,
+	update: undefined,
 } satisfies Defaults<CreateTagsInputProps>;
 
 type TagsInputParts = '' | 'tag' | 'delete-trigger' | 'edit' | 'input';
 const { name, attribute, selector } = createElHelpers<TagsInputParts>('tags-input');
 
 export function createTagsInput(props?: CreateTagsInputProps) {
-	const withDefaults = { ...defaults, ...props } as CreateTagsInputProps;
+	const withDefaults = { ...defaults, ...props } satisfies CreateTagsInputProps;
 
 	// UUID for specific containers
 	const ids = {
@@ -41,8 +46,21 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 		input: generateId(),
 	};
 
-	// Options store
-	const options = writable(omit(withDefaults, 'tags', 'selected'));
+	const options = toWritableStores(omit(withDefaults, 'tags'));
+	const {
+		placeholder,
+		disabled,
+		editable,
+		unique,
+		blur,
+		addOnPaste,
+		allowed,
+		denied,
+		add,
+		remove,
+		update,
+		maxTags,
+	} = options;
 
 	// A store representing the current input value. A readable version is exposed to the
 	// user
@@ -75,37 +93,37 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 
 	// Run validation checks and if a validation fails return false immediately
 	const isInputValid = (v: string) => {
-		const $options = get(options);
 		const $tags = get(tags);
 		const $editing = get(editing);
-
+		const $allowed = get(allowed);
+		const $denied = get(denied);
+		const $maxTags = get(maxTags);
 		// Tag uniqueness
-		if ($options.unique && $editing?.value !== v) {
+		if (get(unique) && $editing?.value !== v) {
 			const index = $tags.findIndex((tag) => tag.value === v);
 			if (index >= 0) return false;
 		}
 
 		// Allowed list is populated and this value is not in it
-		if ($options.allowed && $options.allowed.length > 0 && !$options.allowed.includes(v))
-			return false;
+		if ($allowed && $allowed.length > 0 && !$allowed.includes(v)) return false;
 
 		// Deny list is populated and this value is in it
-		if ($options.denied && $options.denied.length > 0 && $options.denied.includes(v)) return false;
+		if ($denied && $denied.length > 0 && $denied.includes(v)) return false;
 
-		if ($options.maxTags && $options.maxTags > 0 && $tags.length >= $options.maxTags) return false;
+		if ($maxTags && $maxTags > 0 && $tags.length >= $maxTags) return false;
 
 		return true;
 	};
 
 	// Add a tag to the $tags store. Calls `$options.add()` if set
 	const addTag = async (v: string) => {
-		const $options = get(options);
+		const $add = get(add);
 
 		let workingTag = { id: '', value: v };
 
-		if ($options.add) {
+		if ($add) {
 			try {
-				const res = await $options.add(v);
+				const res = await $add(v);
 
 				if (typeof res === 'string') workingTag.value = res;
 				else workingTag = res;
@@ -124,16 +142,16 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 
 	// Update a tag in the $tags store. Calls `$options.update()` if set
 	const updateTag = async (tag: Tag, select = false) => {
-		const $options = get(options);
+		const $update = get(update);
 
 		// Store the id, incase it changes during the update
 		const oldId = tag.id;
 
 		let workingTag = tag;
 
-		if ($options.update) {
+		if ($update) {
 			try {
-				const res = await $options.update(workingTag);
+				const res = await $update(workingTag);
 				workingTag = res;
 
 				// If the id was wiped, give it a new one
@@ -161,11 +179,11 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 
 	// Remove a tag from the $tags store. Calls `$options.remove()` if set
 	const removeTag = async (t: Tag) => {
-		const $options = get(options);
+		const $remove = get(remove);
 
-		if ($options.remove) {
+		if ($remove) {
 			try {
-				if (!(await $options.remove(t))) return false;
+				if (!(await $remove(t))) return false;
 			} catch {
 				return false;
 			}
@@ -182,12 +200,12 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 	};
 
 	const root = builder(name(''), {
-		stores: options,
-		returned: ($options) => {
+		stores: disabled,
+		returned: ($disabled) => {
 			return {
 				'data-melt-id': ids.root,
-				'data-disabled': $options.disabled ? true : undefined,
-				disabled: $options.disabled,
+				'data-disabled': $disabled ? true : undefined,
+				disabled: $disabled,
 			} as const;
 		},
 		action: (node: HTMLElement) => {
@@ -208,13 +226,13 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 	});
 
 	const input = builder(name('input'), {
-		stores: options,
-		returned: ($options) => {
+		stores: [disabled, placeholder],
+		returned: ([$disabled, $placeholder]) => {
 			return {
 				'data-melt-id': ids.input,
-				'data-disabled': $options.disabled ? '' : undefined,
-				disabled: $options.disabled,
-				placeholder: $options.placeholder,
+				'data-disabled': $disabled ? '' : undefined,
+				disabled: $disabled,
+				placeholder: $placeholder,
 			};
 		},
 		action: (node: HTMLInputElement) => {
@@ -264,11 +282,11 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 					if (!value) return;
 
 					// Handle clear or add (if set)
-					const $options = get(options);
+					const $blur = get(blur);
 
-					if ($options.blur === 'clear') {
+					if ($blur === 'clear') {
 						node.value = '';
-					} else if ($options.blur === 'add') {
+					} else if ($blur === 'add') {
 						if (isInputValid(value) && (await addTag(value))) {
 							node.value = '';
 							inputValue.set('');
@@ -284,7 +302,7 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 					if (!pastedText) return;
 
 					// Do nothing when addOnPaste is false
-					if (!get(options).addOnPaste) return;
+					if (!get(addOnPaste)) return;
 
 					// Update value with the pasted text or set invalid
 					if (isInputValid(pastedText) && (await addTag(pastedText))) {
@@ -422,11 +440,11 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 	});
 
 	const tag = builder(name('tag'), {
-		stores: [selected, editing, options],
-		returned: ([$selected, $editing, $options]) => {
+		stores: [selected, editing, disabled, editable],
+		returned: ([$selected, $editing, $disabled, $editable]) => {
 			return (tag: TagProps) => {
-				const disabled = $options.disabled || tag.disabled;
-				const editable = $options.editable && tag.editable !== false;
+				const disabled = $disabled || tag.disabled;
+				const editable = $editable && tag.editable !== false;
 				const selected = disabled ? undefined : $selected?.id === tag?.id;
 				const editing = editable ? $editing?.id === tag?.id : undefined;
 
@@ -519,11 +537,11 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 	});
 
 	const deleteTrigger = builder(name('delete-trigger'), {
-		stores: [selected, editing, options],
-		returned: ([$selected, $editing, $options]) => {
+		stores: [selected, editing, disabled, editable],
+		returned: ([$selected, $editing, $disabled, $editable]) => {
 			return (tag: TagProps) => {
-				const disabled = $options.disabled || tag.disabled;
-				const editable = $options.editable && tag.editable !== false;
+				const disabled = $disabled || tag.disabled;
+				const editable = $editable && tag.editable !== false;
 				const selected = disabled ? undefined : $selected?.id === tag?.id;
 				const editing = editable ? $editing?.id === tag?.id : undefined;
 
@@ -562,10 +580,10 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 	});
 
 	const edit = builder(name('edit'), {
-		stores: [editing, options],
-		returned: ([$editing, $options]) => {
+		stores: [editing, editable],
+		returned: ([$editing, $editable]) => {
 			return (tag: Tag) => {
-				const editable = $options.editable;
+				const editable = $editable;
 				const editing = editable ? $editing?.id === tag.id : undefined;
 
 				return {
@@ -686,16 +704,22 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 	});
 
 	return {
-		inputValue: derived(inputValue, ($inputValue) => $inputValue),
-		inputInvalid: derived(inputInvalid, ($inputInvalid) => $inputInvalid),
-		selected,
-		isSelected,
+		elements: {
+			root,
+			input,
+			deleteTrigger,
+			edit,
+			tag,
+		},
+		states: {
+			tags,
+			inputValue,
+			inputInvalid,
+			selected,
+		},
+		helpers: {
+			isSelected,
+		},
 		options,
-		tags,
-		root,
-		input,
-		tag,
-		deleteTrigger,
-		edit,
 	};
 }
diff --git a/src/lib/builders/tags-input/types.ts b/src/lib/builders/tags-input/types.ts
index 6b9dabc1..baf1735e 100644
--- a/src/lib/builders/tags-input/types.ts
+++ b/src/lib/builders/tags-input/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createTagsInput } from './create';
 
 export type CreateTagsInputProps = {
@@ -35,4 +36,8 @@ export type UpdateTag = (tag: Tag) => Promise<Tag>;
 export type RemoveTag = (tag: Tag) => Promise<boolean>;
 export type AddTag = (tag: string) => Promise<Tag | string>;
 
-export type CreateTagsInputReturn = ReturnType<typeof createTagsInput>;
+export type TagsInput = BuilderReturn<typeof createTagsInput>;
+export type TagsInputElements = TagsInput['elements'];
+export type TagsInputOptions = TagsInput['options'];
+export type TagsInputStates = TagsInput['states'];
+export type TagsInputHelpers = TagsInput['helpers'];
diff --git a/src/lib/builders/toast/create.ts b/src/lib/builders/toast/create.ts
index 39ebe97c..9c708f81 100644
--- a/src/lib/builders/toast/create.ts
+++ b/src/lib/builders/toast/create.ts
@@ -7,13 +7,24 @@ import {
 	generateId,
 	isTouch,
 	noop,
-} from '../../internal/helpers';
-import type { AddToastProps, Toast } from './types';
+	toWritableStores,
+} from '$lib/internal/helpers';
+import type { AddToastProps, CreateToastProps, Toast } from './types';
 
 type ToastParts = 'content' | 'title' | 'description' | 'close';
 const { name } = createElHelpers<ToastParts>('toast');
 
-export function createToasts<T = object>(defaults?: Omit<AddToastProps<T>, 'data'>) {
+const defaults = {
+	closeDelay: 5000,
+	type: 'foreground',
+} satisfies CreateToastProps;
+
+export function createToasts<T = object>(props?: CreateToastProps) {
+	const withDefaults = { ...defaults, ...props } satisfies CreateToastProps;
+
+	const options = toWritableStores(withDefaults);
+	const { closeDelay, type } = options;
+
 	const toastsMap = writable(new Map<string, Toast<T>>());
 	const timeouts = new Map<string, number>();
 
@@ -51,9 +62,8 @@ export function createToasts<T = object>(defaults?: Omit<AddToastProps<T>, 'data
 
 	const addToast = (props: AddToastProps<T>) => {
 		const propsWithDefaults = {
-			closeDelay: 5000,
-			type: 'foreground',
-			...defaults,
+			closeDelay: get(closeDelay),
+			type: get(type),
 			...props,
 		} satisfies AddToastProps<T>;
 
@@ -176,11 +186,18 @@ export function createToasts<T = object>(defaults?: Omit<AddToastProps<T>, 'data
 	});
 
 	return {
-		toasts,
-		addToast,
-		content,
-		title,
-		description,
-		close,
+		elements: {
+			content,
+			title,
+			description,
+			close,
+		},
+		states: {
+			toasts,
+		},
+		helpers: {
+			addToast,
+		},
+		options,
 	};
 }
diff --git a/src/lib/builders/toast/types.ts b/src/lib/builders/toast/types.ts
index a556cb2c..aec5ebbd 100644
--- a/src/lib/builders/toast/types.ts
+++ b/src/lib/builders/toast/types.ts
@@ -1,10 +1,14 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createToasts } from './create';
 
 export type EmptyType = Record<never, never>;
 
-export type AddToastProps<T = object> = {
+export type CreateToastProps = {
 	closeDelay?: number;
 	type?: 'foreground' | 'background';
+};
+
+export type AddToastProps<T = object> = CreateToastProps & {
 	data: T;
 };
 
@@ -20,4 +24,8 @@ export type Toast<T = object> = {
 	data: T;
 };
 
-export type CreateToastReturn = ReturnType<typeof createToasts>;
+export type Toasts = BuilderReturn<typeof createToasts>;
+export type ToastsElements = Toasts['elements'];
+export type ToastsOptions = Toasts['options'];
+export type ToastsStates = Toasts['states'];
+export type ToastsHelpers = Toasts['helpers'];
diff --git a/src/lib/builders/toggle-group/create.ts b/src/lib/builders/toggle-group/create.ts
index 4a5e69ff..33a66386 100644
--- a/src/lib/builders/toggle-group/create.ts
+++ b/src/lib/builders/toggle-group/create.ts
@@ -8,11 +8,12 @@ import {
 	kbd,
 	noop,
 	omit,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import { getElemDirection } from '$lib/internal/helpers/locale';
 import type { Defaults } from '$lib/internal/types';
 import { derived, get, writable } from 'svelte/store';
-import type { CreateToggleGroupProps, ToggleGroupItemProps } from './types';
+import type { CreateToggleGroupProps, ToggleGroupItemProps, ToggleGroupType } from './types';
 
 const defaults = {
 	type: 'single',
@@ -20,59 +21,50 @@ const defaults = {
 	loop: true,
 	rovingFocus: true,
 	disabled: false,
-	value: null,
+	value: '',
 } satisfies Defaults<CreateToggleGroupProps>;
 
 type ToggleGroupParts = 'item';
 const { name, selector } = createElHelpers<ToggleGroupParts>('toggle-group');
 
-export function createToggleGroup(props: CreateToggleGroupProps = {}) {
+export const createToggleGroup = <T extends ToggleGroupType = 'single'>(
+	props?: CreateToggleGroupProps<T>
+) => {
 	const withDefaults = { ...defaults, ...props };
-	const options = writable(omit(withDefaults, 'value'));
-	const value = writable(withDefaults.value);
 
-	options.subscribe((o) => {
-		value.update((v) => {
-			if (o.type === 'single' && Array.isArray(v)) {
-				return null;
-			}
+	const options = toWritableStores(omit(withDefaults, 'value'));
+	const { type, orientation, loop, rovingFocus, disabled } = options;
 
-			if (o.type === 'multiple' && !Array.isArray(v)) {
-				return v === null ? [] : [v];
-			}
-
-			return v;
-		});
-	});
+	const value = writable<string | string[] | undefined>(withDefaults.value);
 
 	const root = builder(name(), {
-		stores: options,
-		returned: ($options) => {
+		stores: orientation,
+		returned: ($orientation) => {
 			return {
 				role: 'group',
-				'data-orientation': $options.orientation,
+				'data-orientation': $orientation,
 			} as const;
 		},
 	});
 
 	const item = builder(name('item'), {
-		stores: [options, value],
-		returned: ([$options, $value]) => {
+		stores: [value, disabled, orientation, type],
+		returned: ([$value, $disabled, $orientation, $type]) => {
 			return (props: ToggleGroupItemProps) => {
 				const itemValue = typeof props === 'string' ? props : props.value;
 				const argDisabled = typeof props === 'string' ? false : !!props.disabled;
-				const disabled = $options.disabled || argDisabled;
+				const disabled = $disabled || argDisabled;
 				const pressed = Array.isArray($value) ? $value.includes(itemValue) : $value === itemValue;
 				return {
 					disabled,
 					pressed,
-					'data-orientation': $options.orientation,
+					'data-orientation': $orientation,
 					'data-disabled': disabled ? true : undefined,
 					'data-state': pressed ? 'on' : 'off',
 					'data-value': itemValue,
 					'aria-pressed': pressed,
 					type: 'button',
-					role: $options.type === 'single' ? 'radio' : undefined,
+					role: $type === 'single' ? 'radio' : undefined,
 					tabindex: pressed ? 0 : -1,
 				} as const;
 			};
@@ -103,17 +95,24 @@ export function createToggleGroup(props: CreateToggleGroupProps = {}) {
 					const { value: itemValue, disabled } = getNodeProps();
 					if (itemValue === undefined || disabled) return;
 
-					value.update((v) => {
-						if (Array.isArray(v)) {
-							return v.includes(itemValue) ? v.filter((i) => i !== itemValue) : [...v, itemValue];
+					value.update(($value) => {
+						if (withDefaults.type === 'single') {
+							return $value === itemValue ? undefined : itemValue;
+						} else {
+							const arrValue = $value as string[] | undefined;
+							if (arrValue === undefined) {
+								return [itemValue];
+							} else {
+								return arrValue.includes(itemValue)
+									? arrValue.filter((v) => v !== itemValue)
+									: [...arrValue, itemValue];
+							}
 						}
-						return v === itemValue ? null : itemValue;
 					});
 				}),
 
 				addEventListener(node, 'keydown', (e) => {
-					const $options = get(options);
-					if (!$options.rovingFocus) return;
+					if (!get(rovingFocus)) return;
 
 					const el = e.currentTarget;
 					if (!isHTMLElement(el)) return;
@@ -128,21 +127,24 @@ export function createToggleGroup(props: CreateToggleGroupProps = {}) {
 					const currentIndex = items.indexOf(el);
 
 					const dir = getElemDirection(el);
+					const $orientation = get(orientation);
 					const nextKey = {
 						horizontal: dir === 'rtl' ? kbd.ARROW_LEFT : kbd.ARROW_RIGHT,
 						vertical: kbd.ARROW_DOWN,
-					}[$options.orientation ?? 'horizontal'];
+					}[$orientation ?? 'horizontal'];
 
 					const prevKey = {
 						horizontal: dir === 'rtl' ? kbd.ARROW_RIGHT : kbd.ARROW_LEFT,
 						vertical: kbd.ARROW_UP,
-					}[$options.orientation ?? 'horizontal'];
+					}[$orientation ?? 'horizontal'];
+
+					const $loop = get(loop);
 
 					if (e.key === nextKey) {
 						e.preventDefault();
 						const nextIndex = currentIndex + 1;
 						if (nextIndex >= items.length) {
-							if ($options.loop) {
+							if ($loop) {
 								handleRovingFocus(items[0]);
 							}
 						} else {
@@ -152,7 +154,7 @@ export function createToggleGroup(props: CreateToggleGroupProps = {}) {
 						e.preventDefault();
 						const prevIndex = currentIndex - 1;
 						if (prevIndex < 0) {
-							if ($options.loop) {
+							if ($loop) {
 								handleRovingFocus(items[items.length - 1]);
 							}
 						} else {
@@ -181,10 +183,16 @@ export function createToggleGroup(props: CreateToggleGroupProps = {}) {
 	});
 
 	return {
+		elements: {
+			root,
+			item,
+		},
+		states: {
+			value,
+		},
+		helpers: {
+			isPressed,
+		},
 		options,
-		value,
-		root,
-		item,
-		isPressed,
 	};
-}
+};
diff --git a/src/lib/builders/toggle-group/types.ts b/src/lib/builders/toggle-group/types.ts
index b2ce2982..1c6203d4 100644
--- a/src/lib/builders/toggle-group/types.ts
+++ b/src/lib/builders/toggle-group/types.ts
@@ -1,17 +1,11 @@
-import type { Orientation } from '$lib/internal/types';
+import type { BuilderReturn, Orientation } from '$lib/internal/types';
 import type { createToggleGroup } from './create';
 
-type SingleToggleGroupRootProps = {
-	type?: 'single';
-	value?: string | null;
-};
-
-type MultipleToggleGroupRootProps = {
-	type: 'multiple';
-	value?: string[];
-};
+export type ToggleGroupType = 'single' | 'multiple';
 
-export type CreateToggleGroupProps = (SingleToggleGroupRootProps | MultipleToggleGroupRootProps) & {
+export type CreateToggleGroupProps<T extends ToggleGroupType = 'single'> = {
+	value?: T extends 'single' ? string : string[];
+	type?: T;
 	disabled?: boolean;
 	rovingFocus?: boolean;
 	loop?: boolean;
@@ -25,4 +19,8 @@ export type ToggleGroupItemProps =
 	  }
 	| string;
 
-export type CreateToggleGroupReturn = ReturnType<typeof createToggleGroup>;
+export type ToggleGroup = BuilderReturn<typeof createToggleGroup>;
+export type ToggleGroupElements = ToggleGroup['elements'];
+export type ToggleGroupOptions = ToggleGroup['options'];
+export type ToggleGroupStates = ToggleGroup['states'];
+export type ToggleGroupHelpers = ToggleGroup['helpers'];
diff --git a/src/lib/builders/toggle/create.ts b/src/lib/builders/toggle/create.ts
index 36cefd45..9a9a9a72 100644
--- a/src/lib/builders/toggle/create.ts
+++ b/src/lib/builders/toggle/create.ts
@@ -1,12 +1,20 @@
-import { addEventListener, builder } from '$lib/internal/helpers';
+import { addEventListener, builder, omit, toWritableStores } from '$lib/internal/helpers';
 import { get, writable } from 'svelte/store';
 import type { CreateToggleProps } from './types';
 
-export function createToggle(props: CreateToggleProps = {}) {
-	const pressed = writable(props.pressed ?? false);
-	const disabled = writable(props.disabled ?? false);
+const defaults = {
+	pressed: false,
+	disabled: false,
+} satisfies CreateToggleProps;
+export function createToggle(props?: CreateToggleProps) {
+	const withDefaults = { ...defaults, ...props } satisfies CreateToggleProps;
 
-	const toggle = builder('toggle', {
+	const options = toWritableStores(omit(withDefaults, 'pressed'));
+	const { disabled } = options;
+
+	const pressed = writable(withDefaults.pressed ?? false);
+
+	const root = builder('toggle', {
 		stores: [pressed, disabled],
 		returned: ([$pressed, $disabled]) => {
 			return {
@@ -31,9 +39,12 @@ export function createToggle(props: CreateToggleProps = {}) {
 	});
 
 	return {
-		pressed,
-		disabled,
-		root: toggle,
-		toggle,
+		elements: {
+			root,
+		},
+		states: {
+			pressed,
+		},
+		options,
 	};
 }
diff --git a/src/lib/builders/toggle/types.ts b/src/lib/builders/toggle/types.ts
index de1d067a..1e9a3d4a 100644
--- a/src/lib/builders/toggle/types.ts
+++ b/src/lib/builders/toggle/types.ts
@@ -1,3 +1,4 @@
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createToggle } from './create';
 
 export type CreateToggleProps = {
@@ -5,4 +6,7 @@ export type CreateToggleProps = {
 	pressed?: boolean;
 };
 
-export type CreateToggleReturn = ReturnType<typeof createToggle>;
+export type Toggle = BuilderReturn<typeof createToggle>;
+export type ToggleElements = Toggle['elements'];
+export type ToggleOptions = Toggle['options'];
+export type ToggleBuilders = Toggle['states'];
diff --git a/src/lib/builders/toolbar/create.ts b/src/lib/builders/toolbar/create.ts
index 98c6dddb..3959779e 100644
--- a/src/lib/builders/toolbar/create.ts
+++ b/src/lib/builders/toolbar/create.ts
@@ -6,11 +6,16 @@ import {
 	handleRovingFocus,
 	isHTMLElement,
 	kbd,
-	omit,
+	toWritableStores,
 } from '$lib/internal/helpers';
 import type { Defaults } from '$lib/internal/types';
-import { derived, get, writable, type Readable } from 'svelte/store';
-import type { CreateToolbarProps, CreateToolbarGroupProps, ToolbarGroupItemProps } from './types';
+import { derived, get, writable } from 'svelte/store';
+import type {
+	CreateToolbarGroupProps,
+	CreateToolbarProps,
+	ToolbarGroupItemProps,
+	ToolbarGroupType,
+} from './types';
 
 const defaults = {
 	loop: true,
@@ -19,16 +24,18 @@ const defaults = {
 
 const { name, selector } = createElHelpers('toolbar');
 
-export function createToolbar(props: CreateToolbarProps = {}) {
-	const withDefaults = { ...defaults, ...props };
-	const toolbarOptions = writable({ ...withDefaults });
+export const createToolbar = (props?: CreateToolbarProps) => {
+	const withDefaults = { ...defaults, ...props } satisfies CreateToolbarProps;
+
+	const options = toWritableStores(withDefaults);
+	const { loop, orientation } = options;
 
 	const root = builder(name(), {
-		stores: toolbarOptions,
-		returned: ($toolbarOptions) => {
+		stores: orientation,
+		returned: ($orientation) => {
 			return {
 				role: 'toolbar',
-				'data-orientation': $toolbarOptions.orientation,
+				'data-orientation': $orientation,
 			};
 		},
 	});
@@ -41,7 +48,7 @@ export function createToolbar(props: CreateToolbarProps = {}) {
 				tabIndex: -1,
 			} as const),
 		action: (node: HTMLElement) => {
-			const unsub = addEventListener(node, 'keydown', getKeydownHandler(toolbarOptions));
+			const unsub = addEventListener(node, 'keydown', handleKeyDown);
 
 			return {
 				destroy: unsub,
@@ -57,7 +64,7 @@ export function createToolbar(props: CreateToolbarProps = {}) {
 				tabIndex: -1,
 			} as const),
 		action: (node: HTMLElement) => {
-			const unsub = addEventListener(node, 'keydown', getKeydownHandler(toolbarOptions));
+			const unsub = addEventListener(node, 'keydown', handleKeyDown);
 
 			return {
 				destroy: unsub,
@@ -66,14 +73,12 @@ export function createToolbar(props: CreateToolbarProps = {}) {
 	});
 
 	const separator = builder(name('separator'), {
-		stores: toolbarOptions,
-		returned: ($toolbarOptions) => {
+		stores: orientation,
+		returned: ($orientation) => {
 			return {
 				role: 'separator',
-				'data-orientation':
-					$toolbarOptions.orientation === 'horizontal' ? 'vertical' : 'horizontal',
-				'aria-orientation':
-					$toolbarOptions.orientation === 'horizontal' ? 'vertical' : 'horizontal',
+				'data-orientation': $orientation === 'horizontal' ? 'vertical' : 'horizontal',
+				'aria-orientation': $orientation === 'horizontal' ? 'vertical' : 'horizontal',
 			} as const;
 		},
 	});
@@ -81,61 +86,53 @@ export function createToolbar(props: CreateToolbarProps = {}) {
 	const groupDefaults = {
 		type: 'single',
 		disabled: false,
-		value: null,
 	} satisfies CreateToolbarGroupProps;
 
-	function createToolbarGroup(props: CreateToolbarGroupProps = {}) {
-		const groupWithDefaults = { ...groupDefaults, ...props };
-		const groupOptions = writable(omit(groupWithDefaults, 'value'));
-
-		const value = writable(groupWithDefaults.value);
+	const createToolbarGroup = <T extends ToolbarGroupType = 'single'>(
+		props?: CreateToolbarGroupProps<T>
+	) => {
+		const groupWithDefaults = { ...groupDefaults, ...props } as CreateToolbarGroupProps<T>;
+		const type = writable(groupWithDefaults.type);
+		const disabled = writable(groupWithDefaults.disabled);
 
-		groupOptions.subscribe((o) => {
-			value.update((v) => {
-				if (o.type === 'single' && Array.isArray(v)) {
-					return null;
-				}
-
-				if (o.type === 'multiple' && !Array.isArray(v)) {
-					return v === null ? [] : [v];
-				}
+		const options = {
+			disabled,
+		};
 
-				return v;
-			});
-		});
+		const value = writable<string | string[] | undefined>(groupWithDefaults.value);
 
 		const { name } = createElHelpers('toolbar-group');
 
 		const root = builder(name(), {
-			stores: toolbarOptions,
-			returned: ($toolbarOptions) => {
+			stores: orientation,
+			returned: ($orientation) => {
 				return {
 					role: 'group',
-					'data-orientation': $toolbarOptions.orientation,
+					'data-orientation': $orientation,
 				} as const;
 			},
 		});
 
 		const item = builder(name('item'), {
-			stores: [groupOptions, value, toolbarOptions],
-			returned: ([$groupOptions, $value, $toolbarOptions]) => {
+			stores: [disabled, type, value, orientation],
+			returned: ([$disabled, $type, $value, $orientation]) => {
 				return (props: ToolbarGroupItemProps) => {
 					const itemValue = typeof props === 'string' ? props : props.value;
 					const argDisabled = typeof props === 'string' ? false : !!props.disabled;
-					const disabled = $groupOptions.disabled || argDisabled;
+					const disabled = $disabled || argDisabled;
 
 					const pressed = Array.isArray($value) ? $value.includes(itemValue) : $value === itemValue;
 
 					return {
 						disabled,
 						pressed,
-						'data-orientation': $toolbarOptions.orientation,
+						'data-orientation': $orientation,
 						'data-disabled': disabled ? true : undefined,
 						'data-value': itemValue,
 						'data-state': pressed ? 'on' : 'off',
 						'aria-pressed': pressed,
 						type: 'button',
-						role: $groupOptions.type === 'single' ? 'radio' : undefined,
+						role: $type === 'single' ? 'radio' : undefined,
 						'data-melt-toolbar-item': '',
 					} as const;
 				};
@@ -164,15 +161,23 @@ export function createToolbar(props: CreateToolbarProps = {}) {
 						const { value: itemValue, disabled } = getNodeProps();
 						if (itemValue === undefined || disabled) return;
 
-						value.update((v) => {
-							if (Array.isArray(v)) {
-								return v.includes(itemValue) ? v.filter((i) => i !== itemValue) : [...v, itemValue];
+						value.update(($value) => {
+							if (groupWithDefaults.type === 'single') {
+								return $value === itemValue ? undefined : itemValue;
+							} else {
+								const arrValue = $value as string[] | undefined;
+								if (arrValue === undefined) {
+									return [itemValue];
+								} else {
+									return arrValue.includes(itemValue)
+										? arrValue.filter((v) => v !== itemValue)
+										: [...arrValue, itemValue];
+								}
 							}
-							return v === itemValue ? null : itemValue;
 						});
 					}),
 
-					addEventListener(node, 'keydown', getKeydownHandler(toolbarOptions))
+					addEventListener(node, 'keydown', handleKeyDown)
 				);
 
 				return {
@@ -188,44 +193,34 @@ export function createToolbar(props: CreateToolbarProps = {}) {
 		});
 
 		return {
-			options: groupOptions,
-			value,
-			root,
-			item,
-			isPressed,
+			elements: {
+				root,
+				item,
+			},
+			states: {
+				value,
+			},
+			helpers: {
+				isPressed,
+			},
+			options,
 		};
-	}
-
-	return {
-		root,
-		options: toolbarOptions,
-		button,
-		link,
-		separator,
-		createToolbarGroup,
 	};
-}
 
-function getToolbarItems(element: HTMLElement) {
-	return Array.from(
-		element.querySelectorAll<HTMLElement>(`${selector('item')}, ${selector('button')}`)
-	);
-}
-
-const getKeydownHandler =
-	(options: Readable<Pick<CreateToolbarProps, 'orientation' | 'loop'>>) => (e: KeyboardEvent) => {
-		const $options = get(options);
+	function handleKeyDown(e: KeyboardEvent) {
+		const $orientation = get(orientation);
+		const $loop = get(loop);
 
 		const dir = 'ltr' as 'ltr' | 'rtl';
 		const nextKey = {
 			horizontal: dir === 'rtl' ? kbd.ARROW_LEFT : kbd.ARROW_RIGHT,
 			vertical: kbd.ARROW_DOWN,
-		}[$options.orientation ?? 'horizontal'];
+		}[$orientation ?? 'horizontal'];
 
 		const prevKey = {
 			horizontal: dir === 'rtl' ? kbd.ARROW_RIGHT : kbd.ARROW_LEFT,
 			vertical: kbd.ARROW_UP,
-		}[$options.orientation ?? 'horizontal'];
+		}[$orientation ?? 'horizontal'];
 
 		const el = e.currentTarget;
 		if (!isHTMLElement(el)) return;
@@ -242,7 +237,7 @@ const getKeydownHandler =
 			e.preventDefault();
 			const nextIndex = currentIndex + 1;
 			if (nextIndex >= items.length) {
-				if ($options.loop) {
+				if ($loop) {
 					handleRovingFocus(items[0]);
 				}
 			} else {
@@ -252,7 +247,7 @@ const getKeydownHandler =
 			e.preventDefault();
 			const prevIndex = currentIndex - 1;
 			if (prevIndex < 0) {
-				if ($options.loop) {
+				if ($loop) {
 					handleRovingFocus(items[items.length - 1]);
 				}
 			} else {
@@ -265,4 +260,24 @@ const getKeydownHandler =
 			e.preventDefault();
 			handleRovingFocus(items[items.length - 1]);
 		}
+	}
+
+	return {
+		elements: {
+			root,
+			button,
+			separator,
+			link,
+		},
+		builders: {
+			createToolbarGroup,
+		},
+		options,
 	};
+};
+
+function getToolbarItems(element: HTMLElement) {
+	return Array.from(
+		element.querySelectorAll<HTMLElement>(`${selector('item')}, ${selector('button')}`)
+	);
+}
diff --git a/src/lib/builders/toolbar/types.ts b/src/lib/builders/toolbar/types.ts
index b28a6a0b..612d489a 100644
--- a/src/lib/builders/toolbar/types.ts
+++ b/src/lib/builders/toolbar/types.ts
@@ -1,24 +1,16 @@
+import type { BuilderReturn, Orientation } from '$lib/internal/types';
 import type { createToolbar } from './create';
 
+export type ToolbarGroupType = 'single' | 'multiple';
+
 export type CreateToolbarProps = {
 	loop?: boolean;
-	orientation?: 'horizontal' | 'vertical';
-};
-
-type SingleToolbarGroupRootProps = {
-	type?: 'single';
-	value?: string | null;
+	orientation?: Orientation;
 };
 
-type MultipleToolbarGroupRootProps = {
-	type: 'multiple';
-	value?: string[];
-};
-
-export type CreateToolbarGroupProps = (
-	| SingleToolbarGroupRootProps
-	| MultipleToolbarGroupRootProps
-) & {
+export type CreateToolbarGroupProps<T extends ToolbarGroupType = 'single'> = {
+	value?: T extends 'single' ? string : string[];
+	type?: T;
 	disabled?: boolean;
 };
 
@@ -28,4 +20,14 @@ export type ToolbarGroupItemProps =
 			disabled?: boolean;
 	  }
 	| string;
-export type CreateToolbarReturn = ReturnType<typeof createToolbar>;
+
+export type Toolbar = BuilderReturn<typeof createToolbar>;
+export type ToolbarElements = Toolbar['elements'];
+export type ToolbarOptions = Toolbar['options'];
+export type ToolbarBuilders = Toolbar['builders'];
+
+export type ToolbarGroup = BuilderReturn<ToolbarBuilders['createToolbarGroup']>;
+export type ToolbarGroupElements = ToolbarGroup['elements'];
+export type ToolbarGroupOptions = ToolbarGroup['options'];
+export type ToolbarGroupStates = ToolbarGroup['states'];
+export type ToolbarGroupHelpers = ToolbarGroup['helpers'];
diff --git a/src/lib/builders/tooltip/create.ts b/src/lib/builders/tooltip/create.ts
index 96d48226..484f9085 100644
--- a/src/lib/builders/tooltip/create.ts
+++ b/src/lib/builders/tooltip/create.ts
@@ -7,6 +7,7 @@ import {
 	noop,
 	omit,
 	styleToString,
+	toWritableStores,
 } from '$lib/internal/helpers';
 
 import { useFloating, usePortal } from '$lib/internal/actions';
@@ -32,8 +33,10 @@ const { name } = createElHelpers<TooltipParts>('tooltip');
 // TODO: Add grace area to prevent tooltip from closing when moving from trigger to tooltip
 
 export function createTooltip(props?: CreateTooltipProps) {
-	const withDefaults = { ...defaults, ...props } as CreateTooltipProps;
-	const options = writable(omit(withDefaults, 'open'));
+	const withDefaults = { ...defaults, ...props } satisfies CreateTooltipProps;
+
+	const options = toWritableStores(omit(withDefaults, 'open'));
+	const { positioning, arrowSize, closeOnPointerDown, openDelay, closeDelay } = options;
 
 	const open = writable(withDefaults.open);
 
@@ -44,7 +47,7 @@ export function createTooltip(props?: CreateTooltipProps) {
 
 	let timeout: number | null = null;
 
-	const openTooltip = derived(options, ($options) => {
+	const openTooltip = derived(openDelay, ($openDelay) => {
 		return () => {
 			if (timeout) {
 				window.clearTimeout(timeout);
@@ -53,11 +56,11 @@ export function createTooltip(props?: CreateTooltipProps) {
 
 			timeout = window.setTimeout(() => {
 				open.set(true);
-			}, $options.openDelay);
+			}, $openDelay);
 		};
 	}) as Readable<() => void>;
 
-	const closeTooltip = derived(options, ($options) => {
+	const closeTooltip = derived(closeDelay, ($closeDelay) => {
 		return () => {
 			if (timeout) {
 				window.clearTimeout(timeout);
@@ -66,7 +69,7 @@ export function createTooltip(props?: CreateTooltipProps) {
 
 			timeout = window.setTimeout(() => {
 				open.set(false);
-			}, $options.closeDelay);
+			}, $closeDelay);
 		};
 	}) as Readable<() => void>;
 
@@ -90,9 +93,7 @@ export function createTooltip(props?: CreateTooltipProps) {
 				addEventListener(node, 'blur', () => open.set(false)),
 				addEventListener(node, 'mousedown', (e) => {
 					e.preventDefault();
-
-					const $options = get(options);
-					if ($options.closeOnPointerDown) {
+					if (get(closeOnPointerDown)) {
 						open.set(false);
 					}
 				})
@@ -127,8 +128,7 @@ export function createTooltip(props?: CreateTooltipProps) {
 					tick().then(() => {
 						const triggerEl = document.getElementById(ids.trigger);
 						if (!triggerEl || node.hidden) return;
-						const $options = get(options);
-						const floatingReturn = useFloating(triggerEl, node, $options.positioning);
+						const floatingReturn = useFloating(triggerEl, node, get(positioning));
 						unsubFloating = floatingReturn.destroy;
 					});
 				} else {
@@ -153,16 +153,24 @@ export function createTooltip(props?: CreateTooltipProps) {
 	});
 
 	const arrow = builder(name('arrow'), {
-		stores: options,
-		returned: ($options) => ({
+		stores: arrowSize,
+		returned: ($arrowSize) => ({
 			'data-arrow': true,
 			style: styleToString({
 				position: 'absolute',
-				width: `var(--arrow-size, ${$options.arrowSize}px)`,
-				height: `var(--arrow-size, ${$options.arrowSize}px)`,
+				width: `var(--arrow-size, ${$arrowSize}px)`,
+				height: `var(--arrow-size, ${$arrowSize}px)`,
 			}),
 		}),
 	});
 
-	return { trigger, open, content, arrow, options };
+	return {
+		elements: {
+			trigger,
+			content,
+			arrow,
+		},
+		states: { open },
+		options,
+	};
 }
diff --git a/src/lib/builders/tooltip/types.ts b/src/lib/builders/tooltip/types.ts
index fce2a6af..9a36af57 100644
--- a/src/lib/builders/tooltip/types.ts
+++ b/src/lib/builders/tooltip/types.ts
@@ -1,4 +1,5 @@
 import type { FloatingConfig } from '$lib/internal/actions';
+import type { BuilderReturn } from '$lib/internal/types';
 import type { createTooltip } from './create';
 
 export type CreateTooltipProps = {
@@ -10,4 +11,7 @@ export type CreateTooltipProps = {
 	closeDelay?: number;
 };
 
-export type CreateTooltipReturn = ReturnType<typeof createTooltip>;
+export type Tooltip = BuilderReturn<typeof createTooltip>;
+export type TooltipElements = Tooltip['elements'];
+export type TooltipOptions = Tooltip['options'];
+export type TooltipStates = Tooltip['states'];
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 7185d19c..4c5fc4b1 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -1,26 +1,3 @@
 export * from './builders';
-
-import type { Action } from 'svelte/action';
-declare global {
-	// eslint-disable-next-line @typescript-eslint/no-namespace
-	namespace svelteHTML {
-		interface HTMLAttributes<T> extends T {
-			/**
-			 * A special attribute for Melt UI's preprocessor `@melt-ui/pp`.
-			 *
-			 * @see https://www.melt-ui.com/docs/preprocessor
-			 *
-			 * @example
-			 * ```svelte
-			 * <script>
-			 * 	const { builder } = createBuilder();
-			 * </script>
-			 *
-			 * <div melt={$builder} />
-			 * ```
-			 */
-			// eslint-disable-next-line @typescript-eslint/no-explicit-any
-			melt?: Record<string, any> & { action: Action<any, any> };
-		}
-	}
-}
+export { melt } from './internal/actions';
+export type { StoreValue, StoreValueObj } from './internal/types';
diff --git a/src/lib/internal/actions/index.ts b/src/lib/internal/actions/index.ts
index 07014f5f..1181af0b 100644
--- a/src/lib/internal/actions/index.ts
+++ b/src/lib/internal/actions/index.ts
@@ -1,5 +1,6 @@
 export * from './click-outside';
 export * from './floating';
 export * from './focus-trap';
+export * from './melt';
 export * from './popper';
 export * from './portal';
diff --git a/src/lib/internal/actions/melt/index.ts b/src/lib/internal/actions/melt/index.ts
new file mode 100644
index 00000000..d7068b1f
--- /dev/null
+++ b/src/lib/internal/actions/melt/index.ts
@@ -0,0 +1,48 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { Action, ActionReturn } from 'svelte/action';
+
+declare global {
+	// eslint-disable-next-line @typescript-eslint/no-namespace
+	namespace svelteHTML {
+		interface HTMLAttributes {
+			/**
+			 * A special attribute for Melt UI's preprocessor `@melt-ui/pp`.
+			 *
+			 * @see https://www.melt-ui.com/docs/preprocessor
+			 *
+			 * @example
+			 * ```svelte
+			 * <script>
+			 * 	const { builder } = createBuilder();
+			 * </script>
+			 *
+			 * <div melt={$builder} />
+			 * ```
+			 */
+			melt?: Record<string, any> & { action: Action<any, any> };
+		}
+	}
+}
+
+type SomeBuilder<
+	E = HTMLElement,
+	P = never,
+	A extends Record<string, any> = Record<never, any>
+> = Record<string, any> & {
+	action: Action<E, P, A>;
+};
+
+type GetActionAttributes<Builder> = Builder extends Record<string, any> & {
+	action: Action<any, any, infer Attr>;
+}
+	? Attr
+	: never;
+
+export function melt<
+	Builder extends SomeBuilder,
+	Element extends HTMLElement,
+	Attributes extends GetActionAttributes<Builder>
+	// eslint-disable-next-line @typescript-eslint/no-unused-vars
+>(node: Element, params: Builder): ActionReturn<Builder, Attributes> {
+	return {};
+}
diff --git a/src/lib/internal/actions/popper/popper.ts b/src/lib/internal/actions/popper/popper.ts
index c5984641..7c20641f 100644
--- a/src/lib/internal/actions/popper/popper.ts
+++ b/src/lib/internal/actions/popper/popper.ts
@@ -18,8 +18,10 @@ const defaultConfig = {
 } satisfies PopperConfig;
 
 export const usePopper: Action<HTMLElement, PopperArgs> = (popperElement, args) => {
-	const { anchorElement, open, options } = args ?? {};
-	if (!(open && anchorElement && options)) return { destroy: noop };
+	const { anchorElement, open, options } = args as PopperArgs;
+	if (!anchorElement || !open || !options) {
+		return { destroy: noop };
+	}
 
 	const opts = { ...defaultConfig, ...options } as PopperConfig;
 
diff --git a/src/lib/internal/actions/portal.ts b/src/lib/internal/actions/portal.ts
index 54037767..93cd2ca4 100644
--- a/src/lib/internal/actions/portal.ts
+++ b/src/lib/internal/actions/portal.ts
@@ -1,11 +1,18 @@
 import { tick } from 'svelte';
 import type { Action } from 'svelte/action';
+import { isHTMLElement, noop } from '../helpers';
 
 export type PortalConfig = string | HTMLElement | undefined;
 
 export const usePortal: Action<HTMLElement, PortalConfig> = (el, target = 'body') => {
 	let targetEl;
 
+	if (!isHTMLElement(target) || typeof target !== 'string') {
+		return {
+			destroy: noop,
+		};
+	}
+
 	async function update(newTarget: HTMLElement | string | undefined) {
 		target = newTarget;
 		if (typeof target === 'string') {
diff --git a/src/lib/internal/helpers/index.ts b/src/lib/internal/helpers/index.ts
index 9a78416c..d3c30b2e 100644
--- a/src/lib/internal/helpers/index.ts
+++ b/src/lib/internal/helpers/index.ts
@@ -13,5 +13,6 @@ export * from './callbacks';
 export * from './debounce';
 export * from './platform';
 export * from './scroll';
+export * from './store';
 export * from './rovingFocus';
 export * from './typeahead';
diff --git a/src/lib/internal/helpers/store.ts b/src/lib/internal/helpers/store.ts
new file mode 100644
index 00000000..8a6eb2b3
--- /dev/null
+++ b/src/lib/internal/helpers/store.ts
@@ -0,0 +1,19 @@
+import { writable, type Writable } from 'svelte/store';
+
+/**
+ * Given an object of properties, returns an object of writable stores
+ * with the same properties and values.
+ */
+export function toWritableStores<T extends Record<string, unknown>>(
+	properties: T
+): { [K in keyof T]: Writable<T[K]> } {
+	const result = {} as { [K in keyof T]: Writable<T[K]> };
+
+	Object.keys(properties).forEach((key) => {
+		const propertyKey = key as keyof T;
+		const value = properties[propertyKey];
+		result[propertyKey] = writable(value);
+	});
+
+	return result;
+}
diff --git a/src/lib/internal/types.ts b/src/lib/internal/types.ts
index bdd26368..750f54fb 100644
--- a/src/lib/internal/types.ts
+++ b/src/lib/internal/types.ts
@@ -34,3 +34,14 @@ export type Orientation = 'horizontal' | 'vertical';
 
 // eslint-disable-next-line @typescript-eslint/ban-types
 export type Prettify<T> = { [K in keyof T]: T[K] } & {};
+
+export type StoreValue<T> = T extends { subscribe(cb: (value: infer V) => void): void } ? V : never;
+
+export type StoreValueObj<T> = {
+	[P in keyof T]: StoreValue<T[P]>;
+};
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type BuilderReturn<T extends (...args: any) => any> = {
+	[P in keyof ReturnType<T>]: ReturnType<T>[P];
+};
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 6e35e4bf..95d3a488 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,8 +1,6 @@
 <script lang="ts">
 	import { fly } from 'svelte/transition';
-	import ArrowRight from '~icons/lucide/arrow-right';
-	import Check from '~icons/lucide/check';
-	import Copy from '~icons/lucide/copy';
+	import { ArrowRight, Check, Copy } from 'lucide-svelte';
 	import { Button } from '$docs/components';
 
 	let copied = false;
diff --git a/src/routes/docs/[...slug]/+page.svelte b/src/routes/docs/[...slug]/+page.svelte
index 76f5a947..afc43341 100644
--- a/src/routes/docs/[...slug]/+page.svelte
+++ b/src/routes/docs/[...slug]/+page.svelte
@@ -7,7 +7,9 @@
 	import { createSeparator } from '$lib';
 	import { page } from '$app/stores';
 
-	const { root: separator } = createSeparator();
+	const {
+		elements: { root: separator },
+	} = createSeparator();
 
 	export let data: PageData;
 	// eslint-disable-next-line no-undef, @typescript-eslint/no-explicit-any
diff --git a/src/routes/docs/builders/[name]/+page.svelte b/src/routes/docs/builders/[name]/+page.svelte
index af3e0c2d..865c5b85 100644
--- a/src/routes/docs/builders/[name]/+page.svelte
+++ b/src/routes/docs/builders/[name]/+page.svelte
@@ -6,7 +6,9 @@
 	import { createSeparator } from '$lib';
 	import { page } from '$app/stores';
 
-	const { root: separator } = createSeparator();
+	const {
+		elements: { root: separator },
+	} = createSeparator();
 
 	export let data: PageData;
 
diff --git a/src/stories/Collapsible/Collapsible.svelte b/src/stories/Collapsible/Collapsible.svelte
index 2325fb9e..20eb3064 100644
--- a/src/stories/Collapsible/Collapsible.svelte
+++ b/src/stories/Collapsible/Collapsible.svelte
@@ -2,14 +2,18 @@
 	import { createCollapsible } from '$lib/builders/collapsible';
 	import { PreviewWrapper } from '$docs/components';
 	import { slide } from 'svelte/transition';
-	import X from '~icons/lucide/x';
-	import ChevronsUpDown from '~icons/lucide/chevrons-up-down';
+	import { X } from 'lucide-svelte';
+	import { ChevronsUpDown } from 'lucide-svelte';
 
 	let openProp: boolean;
 	let disabledProp: boolean;
 	export { openProp as open, disabledProp as disabled };
 
-	const { open, root, content, trigger, disabled } = createCollapsible({
+	const {
+		elements: { root, content, trigger },
+		states: { open },
+		options: { disabled },
+	} = createCollapsible({
 		open: openProp,
 		disabled: disabledProp,
 	});
diff --git a/src/stories/Dialog/BaseDialog.svelte b/src/stories/Dialog/BaseDialog.svelte
index 0943cec7..41a69eeb 100644
--- a/src/stories/Dialog/BaseDialog.svelte
+++ b/src/stories/Dialog/BaseDialog.svelte
@@ -1,6 +1,6 @@
 <script lang="ts">
-	import type { CreateDialogReturn } from '@melt-ui/svelte';
-	import { styleToString } from '@melt-ui/svelte/internal/helpers';
+	import type { Dialog } from '$lib/builders';
+	import { styleToString } from '$lib/internal/helpers';
 	import { cubicOut } from 'svelte/easing';
 	import type { TransitionConfig } from 'svelte/transition';
 
@@ -39,8 +39,12 @@
 		};
 	};
 
-	export let dialog: CreateDialogReturn;
-	const { portal, overlay, content, title, description, close, open } = dialog;
+	export let dialog: Dialog;
+	const {
+		elements: { overlay, content, title, description, close },
+		states: { open },
+		actions: { portal },
+	} = dialog;
 </script>
 
 <div>
diff --git a/src/stories/Dialog/Dialog.svelte b/src/stories/Dialog/Dialog.svelte
index 6424f67f..a4dac6b5 100644
--- a/src/stories/Dialog/Dialog.svelte
+++ b/src/stories/Dialog/Dialog.svelte
@@ -5,7 +5,9 @@
 	import BaseDialog from './BaseDialog.svelte';
 
 	const dialog = createDialog();
-	const { trigger, title, description, close } = dialog;
+	const {
+		elements: { trigger, title, description, close },
+	} = dialog;
 </script>
 
 <PreviewWrapper>
diff --git a/src/stories/Dialog/NestedDialog.svelte b/src/stories/Dialog/NestedDialog.svelte
index 935a36a7..68624eec 100644
--- a/src/stories/Dialog/NestedDialog.svelte
+++ b/src/stories/Dialog/NestedDialog.svelte
@@ -5,10 +5,14 @@
 	import BaseDialog from './BaseDialog.svelte';
 
 	const dialog = createDialog();
-	const { trigger } = dialog;
+	const {
+		elements: { trigger },
+	} = dialog;
 
 	const secondDialog = createDialog();
-	const { trigger: secondTrigger } = secondDialog;
+	const {
+		elements: { trigger: secondTrigger },
+	} = secondDialog;
 </script>
 
 <PreviewWrapper>


Test patch:
diff --git a/src/lib/builders/accordion/tests/AccordionTest.svelte b/src/lib/builders/accordion/tests/AccordionTest.svelte
index 93472ea7..0c728434 100644
--- a/src/lib/builders/accordion/tests/AccordionTest.svelte
+++ b/src/lib/builders/accordion/tests/AccordionTest.svelte
@@ -5,7 +5,10 @@
 	export let disabled: CreateAccordionProps['disabled'];
 	export let items: { id: string; triggerId: string; title: string; description: string }[] = [];
 
-	const { content, item, trigger, isSelected, root } = createAccordion({ type, disabled });
+	const {
+		elements: { root, content, item, trigger },
+		helpers: { isSelected },
+	} = createAccordion({ type, disabled });
 </script>
 
 <div melt={$root}>
diff --git a/src/lib/builders/avatar/tests/AvatarTest.svelte b/src/lib/builders/avatar/tests/AvatarTest.svelte
index d57bf56e..c4186d2e 100644
--- a/src/lib/builders/avatar/tests/AvatarTest.svelte
+++ b/src/lib/builders/avatar/tests/AvatarTest.svelte
@@ -3,7 +3,9 @@
 
 	export let src = 'https://avatars.githubusercontent.com/u/1162160?v=4';
 
-	const { image, fallback } = createAvatar({
+	const {
+		elements: { image, fallback },
+	} = createAvatar({
 		src,
 	});
 </script>
diff --git a/src/lib/builders/collapsible/tests/CollapsibleTest.svelte b/src/lib/builders/collapsible/tests/CollapsibleTest.svelte
index 12c59b61..ef7352af 100644
--- a/src/lib/builders/collapsible/tests/CollapsibleTest.svelte
+++ b/src/lib/builders/collapsible/tests/CollapsibleTest.svelte
@@ -3,7 +3,9 @@
 
 	export let open = false;
 
-	const { root, trigger, content } = createCollapsible({
+	const {
+		elements: { root, trigger, content },
+	} = createCollapsible({
 		open,
 	});
 </script>
diff --git a/src/lib/builders/label/tests/LabelTest.svelte b/src/lib/builders/label/tests/LabelTest.svelte
index b0eab41c..d6dd1a10 100644
--- a/src/lib/builders/label/tests/LabelTest.svelte
+++ b/src/lib/builders/label/tests/LabelTest.svelte
@@ -3,12 +3,14 @@
 
 	export let inputId = 'test';
 
-	const label = createLabel();
+	const {
+		elements: { root },
+	} = createLabel();
 </script>
 
 <form>
 	<div>
-		<label use:label for={inputId}>
+		<label use:root for={inputId}>
 			<span
 				>Test
 				<span>*</span>
diff --git a/src/lib/builders/separator/tests/SeparatorTest.svelte b/src/lib/builders/separator/tests/SeparatorTest.svelte
index 8d21318b..f4b86792 100644
--- a/src/lib/builders/separator/tests/SeparatorTest.svelte
+++ b/src/lib/builders/separator/tests/SeparatorTest.svelte
@@ -4,11 +4,15 @@
 
 	export let orientation: Orientation = 'vertical';
 
-	const { root: vertical } = createSeparator({
+	const {
+		elements: { root: vertical },
+	} = createSeparator({
 		orientation,
 	});
 
-	const { root: horizontalSeparator } = createSeparator({
+	const {
+		elements: { root: horizontalSeparator },
+	} = createSeparator({
 		orientation: 'horizontal',
 		decorative: true,
 	});