diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index c0e1b716f..d769c0f19 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -2,11 +2,16 @@
 
 import {
   createComponent,
+  createSlot,
   createVaporApp,
   defineComponent,
   getCurrentInstance,
+  insert,
   nextTick,
+  prepend,
   ref,
+  renderEffect,
+  setText,
   template,
 } from '../src'
 import { makeRender } from './_utils'
@@ -237,4 +242,190 @@ describe('component: slots', () => {
       'Slot "default" invoked outside of the render function',
     ).not.toHaveBeenWarned()
   })
+
+  describe('createSlot', () => {
+    test('slot should be render correctly', () => {
+      const Comp = defineComponent(() => {
+        const n0 = template('<div></div>')()
+        insert(createSlot('header'), n0 as any as ParentNode)
+        return n0
+      })
+
+      const { host } = define(() => {
+        return createComponent(Comp, {}, { header: () => template('header')() })
+      }).render()
+
+      expect(host.innerHTML).toBe('<div>header</div>')
+    })
+
+    test('slot should be render correctly with binds', async () => {
+      const Comp = defineComponent(() => {
+        const n0 = template('<div></div>')()
+        insert(
+          createSlot('header', { title: () => 'header' }),
+          n0 as any as ParentNode,
+        )
+        return n0
+      })
+
+      const { host } = define(() => {
+        return createComponent(
+          Comp,
+          {},
+          {
+            header: ({ title }) => {
+              const el = template('<h1></h1>')()
+              renderEffect(() => {
+                setText(el, title())
+              })
+              return el
+            },
+          },
+        )
+      }).render()
+
+      expect(host.innerHTML).toBe('<div><h1>header</h1></div>')
+    })
+
+    test('dynamic slot should be render correctly with binds', async () => {
+      const Comp = defineComponent(() => {
+        const n0 = template('<div></div>')()
+        prepend(
+          n0 as any as ParentNode,
+          createSlot('header', { title: () => 'header' }),
+        )
+        return n0
+      })
+
+      const { host } = define(() => {
+        // dynamic slot
+        return createComponent(Comp, {}, {}, () => [
+          { name: 'header', fn: ({ title }) => template(`${title()}`)() },
+        ])
+      }).render()
+
+      expect(host.innerHTML).toBe('<div>header<!--slot--></div>')
+    })
+
+    test('dynamic slot outlet should be render correctly with binds', async () => {
+      const Comp = defineComponent(() => {
+        const n0 = template('<div></div>')()
+        prepend(
+          n0 as any as ParentNode,
+          createSlot(
+            () => 'header', // dynamic slot outlet name
+            { title: () => 'header' },
+          ),
+        )
+        return n0
+      })
+
+      const { host } = define(() => {
+        return createComponent(
+          Comp,
+          {},
+          { header: ({ title }) => template(`${title()}`)() },
+        )
+      }).render()
+
+      expect(host.innerHTML).toBe('<div>header<!--slot--></div>')
+    })
+
+    test('fallback should be render correctly', () => {
+      const Comp = defineComponent(() => {
+        const n0 = template('<div></div>')()
+        insert(
+          createSlot('header', {}, () => template('fallback')()),
+          n0 as any as ParentNode,
+        )
+        return n0
+      })
+
+      const { host } = define(() => {
+        return createComponent(Comp, {}, {})
+      }).render()
+
+      expect(host.innerHTML).toBe('<div>fallback</div>')
+    })
+
+    test('dynamic slot should be updated correctly', async () => {
+      const flag1 = ref(true)
+
+      const Child = defineComponent(() => {
+        const temp0 = template('<p></p>')
+        const el0 = temp0()
+        const el1 = temp0()
+        const slot1 = createSlot('one', {}, () => template('one fallback')())
+        const slot2 = createSlot('two', {}, () => template('two fallback')())
+        insert(slot1, el0 as any as ParentNode)
+        insert(slot2, el1 as any as ParentNode)
+        return [el0, el1]
+      })
+
+      const { host } = define(() => {
+        return createComponent(Child, {}, {}, () => [
+          flag1.value
+            ? { name: 'one', fn: () => template('one content')() }
+            : { name: 'two', fn: () => template('two content')() },
+        ])
+      }).render()
+
+      expect(host.innerHTML).toBe(
+        '<p>one content<!--slot--></p><p>two fallback<!--slot--></p>',
+      )
+
+      flag1.value = false
+      await nextTick()
+
+      expect(host.innerHTML).toBe(
+        '<p>one fallback<!--slot--></p><p>two content<!--slot--></p>',
+      )
+
+      flag1.value = true
+      await nextTick()
+
+      expect(host.innerHTML).toBe(
+        '<p>one content<!--slot--></p><p>two fallback<!--slot--></p>',
+      )
+    })
+
+    test('dynamic slot outlet should be updated correctly', async () => {
+      const slotOutletName = ref('one')
+
+      const Child = defineComponent(() => {
+        const temp0 = template('<p></p>')
+        const el0 = temp0()
+        const slot1 = createSlot(
+          () => slotOutletName.value,
+          {},
+          () => template('fallback')(),
+        )
+        insert(slot1, el0 as any as ParentNode)
+        return el0
+      })
+
+      const { host } = define(() => {
+        return createComponent(
+          Child,
+          {},
+          {
+            one: () => template('one content')(),
+            two: () => template('two content')(),
+          },
+        )
+      }).render()
+
+      expect(host.innerHTML).toBe('<p>one content<!--slot--></p>')
+
+      slotOutletName.value = 'two'
+      await nextTick()
+
+      expect(host.innerHTML).toBe('<p>two content<!--slot--></p>')
+
+      slotOutletName.value = 'none'
+      await nextTick()
+
+      expect(host.innerHTML).toBe('<p>fallback<!--slot--></p>')
+    })
+  })
 })
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index 48ea4509c..dc2da78ea 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -1,8 +1,23 @@
-import { type IfAny, isArray } from '@vue/shared'
-import { baseWatch } from '@vue/reactivity'
-import { type ComponentInternalInstance, setCurrentInstance } from './component'
-import type { Block } from './apiRender'
-import { createVaporPreScheduler } from './scheduler'
+import { type IfAny, isArray, isFunction } from '@vue/shared'
+import {
+  type EffectScope,
+  ReactiveEffect,
+  type SchedulerJob,
+  SchedulerJobFlags,
+  effectScope,
+  isReactive,
+  shallowReactive,
+} from '@vue/reactivity'
+import {
+  type ComponentInternalInstance,
+  currentInstance,
+  setCurrentInstance,
+} from './component'
+import { type Block, type Fragment, fragmentKey } from './apiRender'
+import { renderEffect } from './renderEffect'
+import { createComment, createTextNode, insert, remove } from './dom/element'
+import { queueJob } from './scheduler'
+import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
 
 // TODO: SSR
 
@@ -29,7 +44,7 @@ export const initSlots = (
   rawSlots: InternalSlots | null = null,
   dynamicSlots: DynamicSlots | null = null,
 ) => {
-  const slots: InternalSlots = {}
+  let slots: InternalSlots = {}
 
   for (const key in rawSlots) {
     const slot = rawSlots[key]
@@ -39,50 +54,56 @@ export const initSlots = (
   }
 
   if (dynamicSlots) {
+    slots = shallowReactive(slots)
     const dynamicSlotKeys: Record<string, true> = {}
-    baseWatch(
-      () => {
-        const _dynamicSlots = dynamicSlots()
-        for (let i = 0; i < _dynamicSlots.length; i++) {
-          const slot = _dynamicSlots[i]
-          // array of dynamic slot generated by <template v-for="..." #[...]>
-          if (isArray(slot)) {
-            for (let j = 0; j < slot.length; j++) {
-              slots[slot[j].name] = withCtx(slot[j].fn)
-              dynamicSlotKeys[slot[j].name] = true
-            }
-          } else if (slot) {
-            // conditional single slot generated by <template v-if="..." #foo>
-            slots[slot.name] = withCtx(
-              slot.key
-                ? (...args: any[]) => {
-                    const res = slot.fn(...args)
-                    // attach branch key so each conditional branch is considered a
-                    // different fragment
-                    if (res) (res as any).key = slot.key
-                    return res
-                  }
-                : slot.fn,
-            )
-            dynamicSlotKeys[slot.name] = true
+
+    const effect = new ReactiveEffect(() => {
+      const _dynamicSlots = callWithAsyncErrorHandling(
+        dynamicSlots,
+        instance,
+        VaporErrorCodes.RENDER_FUNCTION,
+      )
+      for (let i = 0; i < _dynamicSlots.length; i++) {
+        const slot = _dynamicSlots[i]
+        // array of dynamic slot generated by <template v-for="..." #[...]>
+        if (isArray(slot)) {
+          for (let j = 0; j < slot.length; j++) {
+            slots[slot[j].name] = withCtx(slot[j].fn)
+            dynamicSlotKeys[slot[j].name] = true
           }
+        } else if (slot) {
+          // conditional single slot generated by <template v-if="..." #foo>
+          slots[slot.name] = withCtx(
+            slot.key
+              ? (...args: any[]) => {
+                  const res = slot.fn(...args)
+                  // attach branch key so each conditional branch is considered a
+                  // different fragment
+                  if (res) (res as any).key = slot.key
+                  return res
+                }
+              : slot.fn,
+          )
+          dynamicSlotKeys[slot.name] = true
         }
-        // delete stale slots
-        for (const key in dynamicSlotKeys) {
-          if (
-            !_dynamicSlots.some(slot =>
-              isArray(slot)
-                ? slot.some(s => s.name === key)
-                : slot?.name === key,
-            )
-          ) {
-            delete slots[key]
-          }
+      }
+      // delete stale slots
+      for (const key in dynamicSlotKeys) {
+        if (
+          !_dynamicSlots.some(slot =>
+            isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
+          )
+        ) {
+          delete slots[key]
         }
-      },
-      undefined,
-      { scheduler: createVaporPreScheduler(instance) },
-    )
+      }
+    })
+
+    const job: SchedulerJob = () => effect.run()
+    job.flags! |= SchedulerJobFlags.PRE
+    job.id = instance.uid
+    effect.scheduler = () => queueJob(job)
+    effect.run()
   }
 
   instance.slots = slots
@@ -98,3 +119,56 @@ export const initSlots = (
     }
   }
 }
+
+export function createSlot(
+  name: string | (() => string),
+  binds?: Record<string, (() => unknown) | undefined>,
+  fallback?: () => Block,
+): Block {
+  let block: Block | undefined
+  let branch: Slot | undefined
+  let oldBranch: Slot | undefined
+  let parent: ParentNode | undefined | null
+  let scope: EffectScope | undefined
+  const isDynamicName = isFunction(name)
+  const instance = currentInstance!
+  const { slots } = instance
+
+  // When not using dynamic slots, simplify the process to improve performance
+  if (!isDynamicName && !isReactive(slots)) {
+    if ((branch = slots[name] || fallback)) {
+      return branch(binds)
+    } else {
+      return []
+    }
+  }
+
+  const getSlot = isDynamicName ? () => slots[name()] : () => slots[name]
+  const anchor = __DEV__ ? createComment('slot') : createTextNode()
+  const fragment: Fragment = {
+    nodes: [],
+    anchor,
+    [fragmentKey]: true,
+  }
+
+  // TODO lifecycle hooks
+  renderEffect(() => {
+    if ((branch = getSlot() || fallback) !== oldBranch) {
+      parent ||= anchor.parentNode
+      if (block) {
+        scope!.stop()
+        remove(block, parent!)
+      }
+      if ((oldBranch = branch)) {
+        scope = effectScope()
+        fragment.nodes = block = scope.run(() => branch!(binds))!
+        parent && insert(block, parent, anchor)
+      } else {
+        scope = block = undefined
+        fragment.nodes = []
+      }
+    }
+  })
+
+  return fragment
+}
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index b15f4c461..919e0c2c6 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -50,6 +50,7 @@ export {
   type FunctionalComponent,
   type SetupFn,
 } from './component'
+export { createSlot } from './componentSlots'
 export { renderEffect } from './renderEffect'
 export {
   watch,