Files
agent-skills/skills/vue-best-practices/reference/slot-renderless-components-vs-composables.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

4.1 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Consider Composables Instead of Renderless Components MEDIUM Renderless slot components add overhead compared to composables for pure logic reuse best-practice
vue3
slots
renderless-components
composables
performance
composition-api

Consider Composables Instead of Renderless Components

Impact: MEDIUM - Renderless components (components that only expose logic via scoped slots without rendering their own UI) were a popular pattern in Vue 2 for logic reuse. In Vue 3, Composition API composables are often more efficient and simpler for the same use case.

Task Checklist

  • Evaluate if a composable can replace a renderless component
  • Use renderless components when slot-based composition is genuinely needed
  • Prefer composables for pure logic/state sharing
  • Consider component overhead vs function overhead

Renderless Component Pattern:

<!-- MouseTracker.vue - Renderless component -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const x = ref(0)
const y = ref(0)

function update(event) {
  x.value = event.pageX
  y.value = event.pageY
}

onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

<template>
  <!-- Only renders the slot, no DOM of its own -->
  <slot :x="x" :y="y" />
</template>
<!-- Usage -->
<MouseTracker v-slot="{ x, y }">
  <div>Mouse: {{ x }}, {{ y }}</div>
</MouseTracker>

Composable Alternative (Recommended):

// composables/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(event: MouseEvent) {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}
<!-- Usage - simpler, no extra component -->
<script setup>
import { useMouse } from '@/composables/useMouse'

const { x, y } = useMouse()
</script>

<template>
  <div>Mouse: {{ x }}, {{ y }}</div>
</template>

When to Use Each Pattern

Pattern Best For
Composables Pure logic reuse, state management, side effects
Renderless Components When slot composition is needed, wrapper logic with flexible children

Use Composables When:

  • Sharing reactive state and methods
  • Encapsulating side effects (event listeners, timers)
  • No need for template slot composition
  • Maximum performance is needed

Use Renderless Components When:

  • Need to provide context/data to arbitrary slot content
  • Building provider components (like context providers)
  • The slot composition pattern is genuinely useful
  • Building component libraries with maximum flexibility

Real-World Examples

Composable is Better:

// Data fetching
export function useFetch<T>(url: string) {
  const data = ref<T | null>(null)
  const error = ref<Error | null>(null)
  const loading = ref(true)
  // ... fetch logic
  return { data, error, loading }
}

// Form validation
export function useForm(initialValues) {
  const values = ref(initialValues)
  const errors = ref({})
  const validate = () => { /* ... */ }
  return { values, errors, validate }
}

Renderless Component is Better:

<!-- Virtualized list that needs to wrap arbitrary item rendering -->
<VirtualScroller :items="items" :item-height="50">
  <template #default="{ item, style }">
    <!-- Consumer controls how each item renders -->
    <div :style="style" class="custom-item">
      {{ item.name }}
    </div>
  </template>
</VirtualScroller>

Performance Consideration

Renderless components still:

  • Create a component instance
  • Go through the Vue component lifecycle
  • Have reactive overhead

Composables are just functions:

  • No component instance overhead
  • Direct reactive primitives
  • Smaller bundle size

Reference