Updated June 2026. Tested on Vue 3 with <script setup>. Part of the Techalyst Vue 3 series.

Every app eventually wants to share logic between components: a piece of state, a fetch helper, a window-size tracker. In Vue 2 the answer was mixins. In Vue 3 the answer is composables, and they fix the real problems mixins always had. Here is what mixins do, where they hurt, and how a composable does the same job cleanly.

How mixins work

A mixin is a plain options object that Vue merges into a component.

// titleMixin.js
export const titleMixin = {
  data() {
    return { city: 'Colombo' }
  },
  computed: {
    capTitle() {
      return this.title.toUpperCase() // assumes the component has `title`
    },
  },
  created() {
    console.log('mixin created')
  },
}
import { titleMixin } from './titleMixin'

export default {
  mixins: [titleMixin],
  data() {
    return { title: 'Hello' }
  },
}

The component now has city and capTitle as if they were declared locally. The merge follows fixed rules: on a name clash the component's own data wins, and when both define the same lifecycle hook both run, with the mixin's firing first. You can also register a mixin globally with app.mixin(...), which applies it to every component in the app.

Where mixins hurt

Mixins work, but three problems show up as an app grows.

1. Unclear sources. Looking at a component template, where does capTitle come from? Local data? A mixin? Which mixin? With several mixins applied, you are hunting through files to find where a property is defined.

2. Name collisions. Two mixins that both define city, or a mixin property that clashes with local state, resolve silently by the merge rules. Nobody warns you that one value quietly shadowed another.

3. Implicit coupling. Notice capTitle reads this.title, but title is not in the mixin, it expects the consuming component to provide it. That hidden contract is invisible until it breaks at runtime.

A global mixin makes all three worse, since it touches every component whether or not it needs the logic.

The composable version

A composable is just a function, by convention named useSomething, that creates reactive state with the Composition API and returns it. No merging, no magic.

// useTitle.js
import { ref, computed } from 'vue'

export function useTitle(initial) {
  const title = ref(initial)
  const capTitle = computed(() => title.value.toUpperCase())
  const city = ref('Colombo')

  return { title, capTitle, city }
}
<script setup>
import { useTitle } from './useTitle'

const { title, capTitle, city } = useTitle('Hello')
</script>

<template>
  <h1>{{ capTitle }}</h1>
  <p>{{ city }}</p>
</template>

Walk back through the three problems and each one disappears:

  • Sources are explicit. capTitle came from useTitle, you can see it in the destructure. No hunting.
  • No silent collisions. If two composables both return city, you rename on destructure: const { city: homeCity } = useTitle(). You are in control.
  • No hidden coupling. The composable takes initial as an argument, so its dependencies are spelled out in the signature instead of reaching into this.

Composables compose, too. One can call another and combine their results, so you build small focused helpers (useFetch, useMouse, useToggle) and stack them. They are the natural home for the kind of logic you would have reached for a mixin to share.

A quick comparison

Mixins Composables
What it is Options object merged in Function you call
Where state comes from Implicit, after merge Explicit, in the return
Name clashes Silent, by merge rules Rename on destructure
Dependencies Hidden (this.title) Passed as arguments
Multiple at once Merge order matters Just call each, compose freely
API style Options API Composition API

Wrapping up

Mixins gave Vue 2 a way to reuse logic, but they hid where things came from, clashed silently, and coupled to the consuming component implicitly. Composables are plain functions that return reactive state, so sources are explicit, clashes are yours to resolve, and dependencies are arguments. For any new Vue 3 code, write a useSomething composable instead of a mixin, and reserve global mixins for the rare cross-cutting case.

More in the series: the setup function in Vue 3 and Vue 3 reactivity. Questions welcome below.