Renderless components in Vue are components that tuck away functionality without dictating how the UI for that functionality should look.
Simply put, renderless components say "here's the functionality you need, it's up to you to use it and style it".
The main benefit is reusability of the core functionality. Imagine you wanted to 'toggle' something on and off. This could be in the form of a toggle button, or a simple text-based toggle.
With renderless components, you don't need two different components for this differing UI.
For the purpose of this article, let's roll with the example of this component. We'll build this with the standard Options API in just a moment.
But first.
Let's start with a really simple example.
export default {
data () {
return {
greeting: 'Hello renderless component'
}
},
render () {
return this.$slots.default({
greeting: this.greeting
})
}
}
Here's how we'd use it.
<Simple>
<template v-slot="{ greeting }">
{{ greeting }}
</template>
</Simple>
The component uses a render
method to pass down, into the default slot, the things we need. To access that stuff, we use a template
element and v-slot
to extract this.
That is essentially what a renderless component is. Rather than output the greeting in the component, we're exposing it, and leaving up to the implementation to decide how that data or functionality gets used.
Here's the renderless component for the toggle functionality we saw above.
export default {
props: {
initialState: {
type: Boolean,
default: false
}
},
data () {
return {
state: this.initialState
}
},
render () {
return this.$slots.default({
state: this.state,
toggle: this.toggle
})
},
methods: {
toggle () {
this.state = !this.state
}
}
}
We have:
initialState
that can be passed in to set the initial on/off state.state
being exposed, which is whether it's toggled or not.toggle
method, also being exposed, that toggles the state between on/off (true
/false
).Here's how we'd use it.
<Toggle>
<template v-slot="{ state, toggle }">
{{ state === true ? 'On' : 'Off' }}
<button v-on:click="toggle">Toggle</button>
</template>
</Toggle>
If you wanted to set the initialState
, that works just like a normal prop.
<Toggle :initialState="true">
<template v-slot="{ state, toggle }">
{{ state === true ? 'On' : 'Off' }}
<button v-on:click="toggle">Toggle</button>
</template>
</Toggle>
With the functionality tucked away and exposed when this component is being used, you're free to implement it again and again with a different UI, as needed.
Here's an example using Tailwind CSS to style the toggle functionality using the same renderless component.
<Toggle>
<template v-slot="{ state, toggle }">
<button v-on:click="toggle" type="button" class="bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" role="switch" :class="{ 'bg-indigo-600': state, 'bg-gray-200': !state }">
<span class="sr-only">Use setting</span>
<span aria-hidden="true" class="translate-x-0 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200" :class="{ 'translate-x-5': state }"></span>
</button>
</template>
</Toggle>
If you were initially confused by the myriad examples around renderless components, I hope this has cleared up your confusion.
There is a lot more you can do with renderless components depending on the complexity of what you're building, but this wasn't intended to cover that – my goal was to explain as simply as possible what renderless components are.
I hope it's helped.