diff --git a/packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts b/packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts
new file mode 100644
index 000000000..31b9cb391
--- /dev/null
+++ b/packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts
@@ -0,0 +1,87 @@
+import type { SetupContext } from '../src/component'
+import {
+  createComponent,
+  defineComponent,
+  ref,
+  template,
+  useAttrs,
+  useSlots,
+} from '../src'
+import { makeRender } from './_utils'
+
+const define = makeRender<any>()
+
+describe('SFC <script setup> helpers', () => {
+  test.todo('should warn runtime usage', () => {})
+
+  test('useSlots / useAttrs (no args)', () => {
+    let slots: SetupContext['slots'] | undefined
+    let attrs: SetupContext['attrs'] | undefined
+
+    const Comp = {
+      setup() {
+        slots = useSlots()
+        attrs = useAttrs()
+      },
+    }
+    const count = ref(0)
+    const passedAttrs = { id: () => count.value }
+    const passedSlots = {
+      default: () => template('')(),
+      x: () => template('')(),
+    }
+
+    const { render } = define({
+      render: () => createComponent(Comp, passedAttrs, passedSlots),
+    })
+    render()
+
+    expect(typeof slots!.default).toBe('function')
+    expect(typeof slots!.x).toBe('function')
+    expect(attrs).toMatchObject({ id: 0 })
+
+    count.value++
+    expect(attrs).toMatchObject({ id: 1 })
+  })
+
+  test('useSlots / useAttrs (with args)', () => {
+    let slots: SetupContext['slots'] | undefined
+    let attrs: SetupContext['attrs'] | undefined
+    let ctx: SetupContext | undefined
+    const Comp = defineComponent({
+      setup(_, _ctx) {
+        slots = useSlots()
+        attrs = useAttrs()
+        ctx = _ctx
+      },
+    })
+    const { render } = define({ render: () => createComponent(Comp) })
+    render()
+    expect(slots).toBe(ctx!.slots)
+    expect(attrs).toBe(ctx!.attrs)
+  })
+
+  describe.todo('mergeDefaults', () => {
+    test.todo('object syntax', () => {})
+    test.todo('array syntax', () => {})
+    test.todo('merging with skipFactory', () => {})
+    test.todo('should warn missing', () => {})
+  })
+
+  describe('mergeModels', () => {
+    test.todo('array syntax', () => {})
+    test.todo('object syntax', () => {})
+    test.todo('overwrite', () => {})
+  })
+
+  test.todo('createPropsRestProxy', () => {})
+
+  describe.todo('withAsyncContext', () => {
+    test.todo('basic', async () => {})
+    test.todo('error handling', async () => {})
+    test.todo('should not leak instance on multiple awaits', async () => {})
+    test.todo('should not leak on multiple awaits + error', async () => {})
+    test.todo('race conditions', async () => {})
+    test.todo('should teardown in-scope effects', async () => {})
+  })
+})
diff --git a/packages/runtime-vapor/src/apiSetupHelpers.ts b/packages/runtime-vapor/src/apiSetupHelpers.ts
new file mode 100644
index 000000000..4afd43046
--- /dev/null
+++ b/packages/runtime-vapor/src/apiSetupHelpers.ts
@@ -0,0 +1,24 @@
+import {
+  type SetupContext,
+  createSetupContext,
+  getCurrentInstance,
+} from './component'
+import { warn } from './warning'
+
+// TODO: warning compiler-macros runtime usages
+
+export function useSlots(): SetupContext['slots'] {
+  return getContext().slots
+}
+
+export function useAttrs(): SetupContext['attrs'] {
+  return getContext().attrs
+}
+
+function getContext(): SetupContext {
+  const i = getCurrentInstance()!
+  if (__DEV__ && !i) {
+    warn(`useContext() called without active instance.`)
+  }
+  return i.setupContext || (i.setupContext = createSetupContext(i))
+}
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index cb8b28daf..b15f4c461 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -110,6 +110,7 @@ export {
   onErrorCaptured,
   // onServerPrefetch,
 } from './apiLifecycle'
+export { useAttrs, useSlots } from './apiSetupHelpers'
 export {
   createVaporApp,
   type App,
diff --git a/playground/src/sub-comp.vue b/playground/src/sub-comp.vue
index 33e7ebd64..7b8ad36c0 100644
--- a/playground/src/sub-comp.vue
+++ b/playground/src/sub-comp.vue
@@ -1,10 +1,10 @@
 <script setup lang="ts">
 import {
-  getCurrentInstance,
   onBeforeMount,
   onBeforeUnmount,
   onMounted,
   onUnmounted,
+  useAttrs,
   watchEffect,
 } from 'vue/vapor'
 
@@ -14,7 +14,7 @@ const props = defineProps<{
   baz: string
 }>()
 
-const attrs = getCurrentInstance()?.attrs
+const attrs = useAttrs()
 
 watchEffect(() => {
   console.log({ ...attrs })
@@ -29,8 +29,12 @@ onUnmounted(() => console.log('sub: unmounted'))
 </script>
 
 <template>
-  <div>sub-comp</div>
-  {{ props }}
-  {{ attrs }}
-  {{ keys(attrs) }}
+  <h2>sub-comp</h2>
+  <p>
+    props: {{ props }}
+    <br />
+    attrs: {{ attrs }}
+    <br />
+    keys(attrs): {{ keys(attrs) }}
+  </p>
 </template>