Vue 3 offers several ways to manage state, and the common mistake is jumping straight to a global store (Pinia) for everything. That over-centralises state and couples unrelated parts of the app. There is a ladder of options from most local to most global, and the skill is choosing the least-global one that fits the scope of the state in question.
Match the pattern to the scope
| Scope of state | Pattern |
|---|---|
| One component | Local ref / reactive |
| Parent to child | Props down, events up |
| Deeply nested subtree | provide / inject |
| Reusable stateful logic | A composable |
| Truly global / cross-app | Pinia store |
Start at the top and only move down as the state's reach genuinely grows. Most state is local; a lot of the rest is parent-child. Only a small fraction is genuinely global, and that is what Pinia is for.
Props and events for parent-child
For state a parent shares with its children, props down and events up is the idiomatic Vue pattern and usually all you need. It keeps data flow explicit and traceable — you can see exactly where state lives and how it changes. Do not reach for a store just to avoid passing a prop through one level; the explicitness is a feature, not a burden.
provide/inject for deep trees
When passing props through many intermediate layers becomes tedious (prop drilling), provide/inject lets an ancestor supply state to any descendant without threading it through every level. It suits things like a theme, a form context, or configuration used deep in a subtree. It is more implicit than props, so use it where the depth genuinely warrants it, not as a casual shortcut.
Composables for shared logic
Often what you want to share is not global state but reusable stateful logic — a composable is the answer. A useFetch or usePagination encapsulates the logic and each caller gets its own instance. For state shared across a few unrelated components without needing a full store, a composable holding module-level reactive state is a lighter option than Pinia.
Every piece of state you put in a global store is state any part of the app can change, which is exactly what makes large apps hard to reason about. Keep state as local as its actual usage allows, climb the ladder only as reach demands, and reserve Pinia for state that is genuinely application-wide. Locality is what keeps an app maintainable.
Vue 3 state management is a ladder — local state, props and events, provide/inject, composables, and finally Pinia — and maintainability comes from choosing the least-global pattern that fits. Resist defaulting to a store; most state belongs closer to where it is used. Match the tool to the scope and the app stays easy to reason about as it grows.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.