Skip to main content

Overview

Factory for creating the trinity pattern tuple used throughout Vuetify Zero. The trinity pattern returns a readonly tuple of [useContext, provideContext, defaultContext], enabling flexible dependency injection with sensible defaults. This pattern is fundamental to all registry-based composables and plugin systems.

Signature

function createTrinity<Z = unknown>(
  useContext: () => Z,
  provideContext: (_context?: Z, app?: App) => Z,
  context: Z
): ContextTrinity<Z>

type ContextTrinity<Z = unknown> = readonly [
  () => Z,                           // useContext
  (context?: Z, app?: App) => Z,     // provideContext wrapper
  Z                                   // default context instance
]

Parameters

useContext
() => Z
required
Function that retrieves/uses the context (typically from createContext).
provideContext
(_context?: Z, app?: App) => Z
required
Function that provides the context to descendants. Should accept an optional context value and optional Vue app instance.
context
Z
required
The default context instance to use when no custom context is provided to provideContext.

Return Value

trinity
ContextTrinity<Z>
A readonly tuple containing:
[0] useContext
() => Z
Retrieves the context from the injection tree.
[1] provideContext
(context?: Z, app?: App) => Z
Provides the context to descendants. If context is omitted, uses the default context from the third element.Returns the provided context value.
[2] context
Z
The default context instance. Useful for accessing default values or creating custom instances.

Usage

import { createContext, createTrinity } from '#v0/composables'
import type { App } from 'vue'

interface FeatureContext {
  enabled: boolean
  toggle: () => void
}

export function createFeature<E extends FeatureContext = FeatureContext>() {
  // Create low-level inject/provide functions
  const [useContext, _provideContext] = createContext<E>('v0:feature')
  
  // Create default context instance
  const context: E = {
    enabled: false,
    toggle: () => { context.enabled = !context.enabled }
  } as E
  
  // Wrap provideContext to use default when no context is passed
  function provideContext(_context: E = context, app?: App): E {
    return _provideContext(_context, app)
  }
  
  // Return trinity
  return createTrinity<E>(useContext, provideContext, context)
}

// Usage
const [useFeature, provideFeature, defaultFeature] = createFeature()

// Provide default context
provideFeature() // uses defaultFeature

// Provide custom context
const customContext = { enabled: true, toggle: () => {} }
provideFeature(customContext)

// Access default values
console.log(defaultFeature.enabled) // false

Trinity Pattern Benefits

The third element provides a default context instance, eliminating the need to pass context when the default is sufficient:
const [useTheme, provideTheme, defaultTheme] = createTheme()

// Use default
provideTheme()

// Or customize
provideTheme({ ...defaultTheme, dark: true })
All Vuetify Zero composables that use dependency injection follow the trinity pattern, creating a consistent API:
// All follow the same pattern
const [use, provide, defaults] = createFeature()
const [use, provide, defaults] = createRegistry()
const [use, provide, defaults] = createSelection()
The third element allows direct access to default values without injection:
const [, , defaults] = createBreakpoints()
console.log(defaults.xs) // Access default breakpoint values
The readonly tuple preserves exact types through generic parameters:
interface MyContext { value: number }

const trinity = createTrinity<MyContext>(
  useContext,
  provideContext,
  context
)

const [use] = trinity
const ctx = use() // ctx is typed as MyContext

Integration with createPlugin

The trinity pattern integrates seamlessly with createPlugin for plugin development:
import { createContext, createTrinity, createPlugin } from '#v0/composables'

interface LoggerContext {
  log: (msg: string) => void
}

function createLogger() {
  const [useContext, _provideContext] = createContext<LoggerContext>('v0:logger')
  
  const context: LoggerContext = {
    log: (msg) => console.log(`[LOG] ${msg}`)
  }
  
  function provideContext(_context: LoggerContext = context, app?: App) {
    return _provideContext(_context, app)
  }
  
  return createTrinity(useContext, provideContext, context)
}

// Create trinity
const [useLogger, provideLogger, loggerContext] = createLogger()

// Create plugin using trinity
export const LoggerPlugin = createPlugin({
  namespace: 'v0:logger',
  provide: (app) => {
    provideLogger(loggerContext, app)
  }
})

// Export for consumers
export { useLogger, provideLogger, loggerContext }
Or use the higher-level createPluginContext which handles this automatically:
import { createPluginContext } from '#v0/composables'

// Automatically creates trinity + plugin + consumer
export const [createLoggerContext, createLoggerPlugin, useLogger] =
  createPluginContext('v0:logger', () => createLogger())

// Returns trinity from createLoggerContext
const [use, provide, context] = createLoggerContext()

Best Practices

Use Vue’s reactive or ref for contexts that need reactivity:
import { reactive } from 'vue'

const context = reactive({
  count: 0,
  increment() { this.count++ }
})
Use generic type parameters to allow context extension:
export function createFeature<E extends FeatureContext = FeatureContext>() {
  // ...
  return createTrinity<E>(useContext, provideContext, context)
}

// Consumers can extend
interface CustomFeature extends FeatureContext {
  extra: string
}
const [use, provide] = createFeature<CustomFeature>()
Export the entire trinity for maximum flexibility:
// ✅ Export destructured trinity
export const [useFeature, provideFeature, featureContext] = createFeature()

// ❌ Don't export just the function
export function useFeature() { /* ... */ }

createContext

Create type-safe injection contexts

createPlugin

Create Vue plugins with trinity pattern

Build docs developers (and LLMs) love