diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts
index 2b58bc3fc43..f8755e7e765 100644
--- a/packages/runtime-core/src/components/BaseTransition.ts
+++ b/packages/runtime-core/src/components/BaseTransition.ts
@@ -24,7 +24,7 @@ import { SchedulerJobFlags } from '../scheduler'
 
 type Hook<T = () => void> = T | T[]
 
-const leaveCbKey: unique symbol = Symbol('_leaveCb')
+export const leaveCbKey: unique symbol = Symbol('_leaveCb')
 const enterCbKey: unique symbol = Symbol('_enterCb')
 
 export interface BaseTransitionProps<HostElement = RendererElement> {
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 05c4ac345eb..b0edfcfe2f3 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -85,7 +85,7 @@ import { initFeatureFlags } from './featureFlags'
 import { isAsyncWrapper } from './apiAsyncComponent'
 import { isCompatEnabled } from './compat/compatConfig'
 import { DeprecationTypes } from './compat/compatConfig'
-import type { TransitionHooks } from './components/BaseTransition'
+import { type TransitionHooks, leaveCbKey } from './components/BaseTransition'
 
 export interface Renderer<HostElement = RendererElement> {
   render: RootRenderFunction<HostElement>
@@ -2057,6 +2057,12 @@ function baseCreateRenderer(
           }
         }
         const performLeave = () => {
+          // #13153 move kept-alive node before v-show transition leave finishes
+          // it needs to call the leaving callback to ensure element's `display`
+          // is `none`
+          if (el!._isLeaving) {
+            el![leaveCbKey](true /* cancelled */)
+          }
           leave(el!, () => {
             remove()
             afterLeave && afterLeave()
diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts
index 14441bd823b..68f7075828c 100644
--- a/packages/vue/__tests__/e2e/Transition.spec.ts
+++ b/packages/vue/__tests__/e2e/Transition.spec.ts
@@ -1722,6 +1722,107 @@ describe('e2e: Transition', () => {
       },
       E2E_TIMEOUT,
     )
+
+    // #13153
+    test(
+      'move kept-alive node before v-show transition leave finishes',
+      async () => {
+        await page().evaluate(() => {
+          const { createApp, ref } = (window as any).Vue
+          const show = ref(true)
+          createApp({
+            template: `
+            <div id="container">
+              <KeepAlive :include="['Comp1', 'Comp2']">
+                <component :is="state === 1 ? 'Comp1' : 'Comp2'"/>
+              </KeepAlive>
+            </div>
+            <button id="toggleBtn" @click="click">button</button>
+          `,
+            setup: () => {
+              const state = ref(1)
+              const click = () => (state.value = state.value === 1 ? 2 : 1)
+              return { state, click }
+            },
+            components: {
+              Comp1: {
+                components: {
+                  Item: {
+                    name: 'Item',
+                    setup() {
+                      return { show }
+                    },
+                    template: `
+                      <Transition name="test">
+                        <div v-show="show" >
+                          <h2>{{ show ? "I should show" : "I shouldn't show " }}</h2>
+                        </div>
+                      </Transition>
+                    `,
+                  },
+                },
+                name: 'Comp1',
+                setup() {
+                  const toggle = () => (show.value = !show.value)
+                  return { show, toggle }
+                },
+                template: `
+                  <Item />
+                  <h2>This is page1</h2>
+                  <button id="changeShowBtn" @click="toggle">{{ show }}</button>
+                `,
+              },
+              Comp2: {
+                name: 'Comp2',
+                template: `<h2>This is page2</h2>`,
+              },
+            },
+          }).mount('#app')
+        })
+
+        expect(await html('#container')).toBe(
+          `<div><h2>I should show</h2></div>` +
+            `<h2>This is page1</h2>` +
+            `<button id="changeShowBtn">true</button>`,
+        )
+
+        // trigger v-show transition leave
+        await click('#changeShowBtn')
+        await nextTick()
+        expect(await html('#container')).toBe(
+          `<div class="test-leave-from test-leave-active"><h2>I shouldn't show </h2></div>` +
+            `<h2>This is page1</h2>` +
+            `<button id="changeShowBtn">false</button>`,
+        )
+
+        // switch to page2, before leave finishes
+        // expect v-show element's display to be none
+        await click('#toggleBtn')
+        await nextTick()
+        expect(await html('#container')).toBe(
+          `<div class="test-leave-from test-leave-active" style="display: none;"><h2>I shouldn't show </h2></div>` +
+            `<h2>This is page2</h2>`,
+        )
+
+        // switch back to page1
+        // expect v-show element's display to be none
+        await click('#toggleBtn')
+        await nextTick()
+        expect(await html('#container')).toBe(
+          `<div class="test-enter-from test-enter-active" style="display: none;"><h2>I shouldn't show </h2></div>` +
+            `<h2>This is page1</h2>` +
+            `<button id="changeShowBtn">false</button>`,
+        )
+
+        await transitionFinish()
+        expect(await html('#container')).toBe(
+          `<div class="" style="display: none;"><h2>I shouldn't show </h2></div>` +
+            `<h2>This is page1</h2>` +
+            `<button id="changeShowBtn">false</button>`,
+        )
+      },
+      E2E_TIMEOUT,
+    )
   })
 
   describe('transition with Suspense', () => {