Reusable UI components often break once you change the design. Solution? Go headless.
A headless component:
"Exposes data & logic (but no styling)"
"Lets you plug in any markup/UX you want"
"Keeps behavior testable and DRY"
Example: a headless dropdown:
const useDropdown = () => {
const isOpen = ref(false)
const toggle = () => isOpen.value = !isOpen.value
return { isOpen, toggle }
}
<template>
<button @click="toggle">Toggle</button>
<div v-if="isOpen"><slot /></div>
</template>
Benefits:
"📦 Easy to theme"
"✅ Easier snapshot testing"
"🧠 Decouples logic from layout"
This is what libraries like Radix and Headless UI follow. You can build your own system for any internal tools.
Top comments (2)
Makes sense, but where do you add the styling afterward, assuming you’re using something inline like Tailwind?
I would use Tailwind as a personal preference and a strong recommendation , but the point is you're free to use the same functionality with multiple markups and styles (Tailwind, SCSS, CSS modules, CSS in JS, etc...).