This package provides a way to use DOM hierarchy to manage context state.
It's framework-agnostic* and not limited to Astro.
* - each tile here is a different labeled UI framework
Become a 🧙Stargazer | Support the author
npm install astro-context- Create a context - Define your shared state:
// src/contexts/counter.ts
import { createContext } from 'astro-context'
export const CounterContext = createContext('counter', 0)- Wrap components with ContextProvider - Provide scope for your components:
---
// src/pages/index.astro
import { ContextProvider } from 'astro-context'
import ReactCounter from '../components/ReactCounter'
import VueCounter from '../components/VueCounter'
import CounterContext from '../contexts/counter'
---
<ContextProvider context={CounterContext}>
<ReactCounter client:load />
<VueCounter client:load />
</ContextProvider>- Use context in components - Access and modify state in your framework components:
// React component
import { useContext } from 'astro-context/react'
import { CounterContext } from '../contexts/counter'
export default function ReactCounter() {
const ref = useRef<HTMLDivElement>(null)
const [count, setCount] = useContext(CounterContext, ref)
return (
<div ref={ref}>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}<!-- Vue component -->
<script setup>
import { useContext } from 'astro-context/vue'
import { CounterContext } from '../contexts/counter'
const [count, setCount] = useContext(CounterContext)
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="setCount(count + 1)">Increment</button>
</div>
</template>- Framework Agnostic: Works with React, Preact, SolidJS, Vue, Svelte, and vanilla JS (Alpine.js included)
- Scoped State: Create isolated state scopes using
ContextProvider - Persistence: Scopes data can be persisted to localStorage
- TypeScript: Full TypeScript support with proper type inference
- Astro Optimized: Designed specifically for Astro's island architecture
- yet the context mechanism can be useful in any web application
Creates a new Store with a stable scope name.
Useful if you want multiple subtrees to share the same context scope.
It also allows you to create persistent scope (to sync via localStorage for example).
Signature:
createStore: (name: string) => Storename - Stable name for the store scope
Returns:
A new Store instance
const context = createContext('local', false, { storage: 'localStorage' })
const sharedStore = createStore('local_1')
// . . .
<ContextProvider context={LocalContext} store={sharedStore}>...</ContextProvider>
// . . .
<ContextProvider context={LocalContext} store={sharedStore}>...</ContextProvider>Gets the default global store instance.
Useful if you want to create a global-scoped subtree under another scope.
Signature:
getDefaultStore: () => StoreReturns:
The default global store
const globalStore = getDefaultStore()
// . . .
<ContextProvider context={LocalContext}>
<ContextProvider context={LocalContext} store={globalStore}>...</ContextProvider>
</ContextProvider>Creates a ScopedContext instance for the given context, automatically determining the appropriate store scope.
This function analyzes the DOM hierarchy to find the correct ContextProvider scope or falls back to global scope.
Signature:
useContext: <T>(context: Context<T, any>, node?: HTMLElement) =>
ScopedContext<T>context - The context to create a scoped instance for
node - Optional DOM element to analyze for context scope (defaults to global scope)
Returns:
A ScopedContext instance bound to the appropriate store
const context = useContext(LocalContext, node)
context.listen((value) => console.log('Changed:', value))
context.value = trueCreates a new Context instance with the specified name, initial value, and configuration.
This is the primary way to create context instances for state management.
Signature:
createContext: CreateContextFnname - Unique identifier for the context
initialValue - Initial value or function that returns the initial value
config - Optional configuration for storage and serialization
Returns
A new Context instance
Example
const LocalContext = createContext('local', false)
const SessionContext = createContext('session', false, {
storage: 'localStorage',
})A context object that manages state across different scopes and storage types.
Provides methods to get, set, and manage context values with support for memory and localStorage persistence.
Signature:
declare class Context<T, _Info extends [`storage:${StorageVariants}`]>The unique name identifier for this context.
Signature:
readonly name: string;Retrieves the current value of the context for the specified scope.
Signature:
get<S extends string | undefined = undefined>(scope?: S): S extends undefined ? T : T | undefined;scope - Optional scope identifier. If undefined, returns the global value
Returns:
The context value for the specified scope
const value = LocalContext.get()
const scopedValue = LocalContext.get('my-scope')Generates provider traits for use in ContextProvider components.
Signature:
providerTraits(store?: Store): {
'data-context-id': string;
'data-context-scope': string;
'data-context-stable': boolean | undefined;
};store - Optional store instance to generate traits for
Returns:
Object containing data attributes for the provider
const traits = LocalContext.providerTraits()
// . . .
<context-provider {...traits}>Sets the value of the context for the specified scope.
Signature:
set(value: T, scope?: Context.inferStorage<Context<any, _Info>> extends 'memory' ? string : 'global'): void;value - The new value to set
scope - Optional scope identifier. Defaults to 'global'
LocalContext.set(true)
LocalContext.set(false, 'my-scope')Configuration class for Context instances that defines storage type and serialization behavior.
Controls how context values are stored and serialized across different storage mechanisms.
Signature:
declare class ContextConfig<T, Storage extends StorageVariants = 'memory'>The storage type to use for this context.
Signature:
readonly storage: Storage;Function used to serialize values for storage.
Signature:
readonly stringify: (value: T) => string;A scoped context provides a convenient interface for interacting with a context within a specific store scope.
It wraps the store's methods and provides reactive value access through getter/setter properties.
Signature:
declare class ScopedContext<T>Subscribes to changes in the context value without immediately calling the callback.
Signature:
listen: (callback: (value: T) => void) => () => void;Subscribes to changes in the context value and immediately calls the callback with the current value.
Signature:
subscribe: (callback: (value: T) => void) => () => void;The current value of the context.
Can be set to update the context value.
Signature:
get value(): T;
set value(value: T);const currentValue = scopedContext.value
scopedContext.value = trueA store manages context values within a specific scope and provides subscription capabilities.
Stores can be scoped to different contexts and provide reactive updates when values change.
Signature:
declare class StoreThe unique scope identifier for this store.
Signature:
readonly scope: string;Optional stable name for the scope, used for persistent scopes.
Signature:
readonly scopeName: string | undefined;Retrieves the current value of a context from this store's scope.
Signature:
get<T>(context: Context<T, any>): T;context - The context to get the value from
Returns:
The current value of the context
const value = store.get(LocalContext)Subscribes to changes in a context value without immediately calling the callback.
Signature:
listen<T>(context: Context<T, any>, callback: (value: T) => void): () => void;context - The context to listen to
callback - Function to call when the value changes
Returns:
Unsubscribe function
const unsubscribe = store.listen(LocalContext, (value) => {
console.log('Value changed:', value)
})
unsubscribe() // Stop listeningSets the value of a context in this store's scope and notifies subscribers.
Signature:
set<T>(context: Context<T, any>, value: T): void;context - The context to set the value for
value - The new value to set
store.set(LocalContext, true)Subscribes to changes in a context value and immediately calls the callback with the current value.
Signature:
subscribe<T>(context: Context<T, any>, callback: (value: T) => void): () => void;context - The context to subscribe to
callback - Function to call when the value changes
Returns:
Unsubscribe function
const unsubscribe = store.subscribe(LocalContext, (value) => {
console.log('Current value:', value)
})
unsubscribe() // Stop listeningInfers the info type from a Context instance.
Signature:
type inferInfo<C extends Context<any, any>> =
C extends Context<any, infer Info> ? Info : neverInfers the storage type from a Context instance.
Signature:
type inferStorage<C extends Context<any, any>> =
C extends Context<any, infer Storage>
? Storage extends [`storage:${infer S}`]
? S
: never
: neverInfers the storage type from a ContextConfig instance.
Signature:
type inferStorage<C extends ContextConfig<any, any>> =
C extends ContextConfig<infer Storage>
? Storage
: inferStorage<ContextConfig<any>>Function type for initializing context values, optionally using persisted data.
Signature:
type _GetInitialValueFn<T> = (persistedValue?: string) => TpersistedValue - Optional persisted value from storage (string for localStorage, undefined for memory)
Returns
The initial value for the context
Available storage variants for context persistence.
Signature:
type StorageVariants = 'memory' | 'localStorage'Note
Each utility is based on the core API, so you can use it to build your own utilities.
See src/<supported-framework>/index.ts for reference.
Creates a ScopedContext instance for the given context, automatically determining the appropriate store scope.
Signature:
useContext: <T>(context: Context<T, any>, nodeOrRef?) => [value, setValue]// React > *.{jsx,tsx}
import { useContext } from 'astro-context/react'
// Preact > *.{jsx,tsx}
import { useContext } from 'astro-context/preact'
// SolidJS > *.{jsx,tsx}
import { useContext } from 'astro-context/solid'
// Vue.js > *.vue
import { useContext } from 'astro-context/vue'
// Svelte > *.svelte
import { useContext } from 'astro-context/svelte'