💚 Vue.js Advanced

What is Vue's renderless component and headless UI pattern?

Answer

Renderless components (headless UI) separate behavior and state from the visual presentation. The component provides all the logic, state, and accessibility via scoped slots — consumers provide the markup. Advanced example — custom select/combobox: // HeadlessSelect.vue <script setup> const props = defineProps(["options", "modelValue"]); const emit = defineEmits(["update:modelValue"]); const isOpen = ref(false); const selected = computed(() => props.options.find(o => o.value === props.modelValue)); const activeIndex = ref(-1); function open() { isOpen.value = true; } function close() { isOpen.value = false; } function select(option) { emit("update:modelValue", option.value); close(); } function handleKeydown(e) { if (e.key === "ArrowDown") activeIndex.value = Math.min(activeIndex.value + 1, props.options.length - 1); if (e.key === "ArrowUp") activeIndex.value = Math.max(activeIndex.value - 1, 0); if (e.key === "Enter") select(props.options[activeIndex.value]); if (e.key === "Escape") close(); } </script> <template><slot :isOpen="isOpen" :selected="selected" :open="open" :close="close" :select="select" :handleKeydown="handleKeydown" :options="props.options" :activeIndex="activeIndex" /></template>. Usage: any visual style while keeping the same logic: <HeadlessSelect :options="opts" v-model="value" v-slot="{ isOpen, selected, open, close, select }"> <button @click="open">{{ selected?.label }}</button> <div v-if="isOpen"><button v-for="opt in opts" @click="select(opt)">{{ opt.label }}</button></div> </HeadlessSelect>. Headless UI components (Radix Vue, Headless UI for Vue) are production-ready implementations of this pattern with full accessibility.