In Vue 3, what is a renderless component built on scoped slots, and when is it still a better choice than a composable?
answer
- logic in, markup out
- the whole template is one slot
- an extra component instance
- logic plus layout composition
basics
~20 sA Vue renderless component holds state and behaviour but renders only a scoped slot, handing its data and functions to the caller's markup. Composables usually do this more cheaply; a renderless component still wins when the reuse includes template structure or slot composition.
solid answer
~50 sA **renderless component** encapsulates logic and delegates all visual output: its template is essentially `<slot :sorted="sorted" :toggle="toggle" />`, and the caller writes `<SortableRows :rows="rows" v-slot="{ sorted, toggle }">` with its own markup inside. It was the main logic-reuse pattern before the Composition API. The Vue docs now note that most of what renderless components do is achieved more efficiently with a **composable**, because a composable adds no extra component instance or nesting and its return values are plain refs usable anywhere in `<script setup>`. A slot-based component remains the better tool when the reusable part includes **template structure** as well as logic, like a list that owns loading, pagination and the list markup while delegating each item's look to a scoped slot, or when the logic must live inside the template tree, such as tying its lifetime to a `v-if` region.
go deeper
Know that a renderless component exposes state through a scoped slot and lets the caller write all the markup.
Build one with a slot-only template and explain why its slot props are available only inside the fragment.
Choose between a composable and a slot-based component for a real feature, citing instance cost, where the state is needed and whether markup is part of the reuse.
Set a library-wide rule for headless behaviour: composables by default, slot-based components only where structure is shared, and a stable documented slot API.
## What a renderless component is In Vue, a scoped slot lets a child pass data to the caller's template fragment. A **renderless component** pushes that idea to the limit: the component owns state, computed values and methods, and renders **nothing of its own**. Its whole template is a slot outlet that exposes that state: ```vue <!-- SortableRows.vue --> <script setup lang="ts"> import { computed, ref } from 'vue' const props = defineProps<{ rows: { name: string; total: number }[] }>() const key = ref<'name' | 'total'>('name') const asc = ref(true) const sorted = computed(() => [...props.rows].sort((a, b) => { const d = a[key.value] < b[key.value] ? -1 : a[key.value] > b[key.value] ? 1 : 0 return asc.value ? d : -d }), ) function toggle(k: 'name' | 'total') { if (key.value === k) asc.value = !asc.value else { key.value = k; asc.value = true } } </script> <template> <slot :sorted="sorted" :toggle="toggle" :sort-key="key" :asc="asc" /> </template> ``` The caller supplies all markup, and Vue hands it camelCased slot props: ```vue <SortableRows :rows="orders" v-slot="{ sorted, toggle, sortKey }"> <table> <tr><th @click="toggle('name')">Name {{ sortKey === 'name' ? '*' : '' }}</th></tr> <tr v-for="o in sorted" :key="o.name"><td>{{ o.name }}</td></tr> </table> </SortableRows> ``` ## How it compares with a composable The same sorting logic can be a composable, `useSorting(rows)`, returning `{ sorted, toggle, sortKey }` as refs. The trade-offs: | Aspect | Renderless component | Composable | |---|---|---| | Extra component instance | Yes, one per use | No | | Where the state is usable | Only inside the slot fragment | Anywhere in the calling component's script and template | | Combining several | Nested components, deeper templates | Several calls in one `setup` | | Can own template structure | Yes | No, it returns data only | | Lifetime | Mounted and unmounted with its place in the tree | Tied to the calling component | The Vue docs draw the conclusion directly: most renderless-component use cases are better served by composables, which avoid the overhead of extra component nesting. ## When a slot-based component still wins 1. **Logic plus layout.** A `FancyList`-style component owns fetching, pagination and the `<ul>` markup, and exposes a scoped `#item` slot for each row's look. A composable cannot supply the list markup; the component can. 2. **Lifetime by template position.** Wrapping a region in a component ties the logic's setup and teardown to that region's `v-if` or `v-for` entry, without extra bookkeeping in the parent. 3. **Declarative composition for template-heavy teams.** Some libraries expose headless components so consumers compose behaviour purely in templates. ## Judgment checklist - If the reusable part is **only logic**, write a composable. - If it is **logic plus structure**, write a component with scoped slots for the parts callers customise. - Avoid stacking several renderless wrappers around one template; the nesting makes the data flow hard to follow and each layer is a component instance. - Keep slot prop names stable and documented; they are the renderless component's entire API. ## Why interviewers ask The question tests whether a candidate knows the pattern, can build one, and has absorbed the Vue 3 guidance that composables are the default, while still recognising the cases where a slot-based component is the cleaner design. A strong answer names the concrete cost (an extra component instance per use), the concrete limitation (slot props are visible only inside the fragment, never in the caller's script), and one case where shared markup makes the component the right call. A weak answer either treats renderless components as the modern default or dismisses scoped slots entirely.
- In a Vue 3 renderless component, why can the parent's `<script setup>` not use the `sorted` value it exposes?Slot props exist only as the arguments of the slot fragment, which is a function the child calls during its own render. The parent's script never receives them; they are visible only inside that fragment's template. If the parent's script needs the value, the logic belongs in a composable called in the parent instead.
- What does each use of a Vue 3 renderless component cost that a composable call does not?A component instance: its own setup, render effect, props normalisation and a level of nesting in the tree and devtools. For one use it is negligible; for a renderless wrapper inside every row of a large list, or several nested wrappers, the extra instances add up.
saying these in an interview costs you the question
- A renderless component renders nothing at all, including the caller's slot content.
- Renderless components are the recommended default for logic reuse in Vue 3.
- The parent's script can read a renderless component's slot props directly.
- A composable can also provide template markup for the caller to reuse.
- Renderless components need a render function; templates cannot express them.