use-storage
Typed built-in serializers, usable directly as the serializer option.
| Package | @vizejs/composable/use-storage |
| Own the source | vize lib pull composable:use-storage |
| Runtime exports | inferStorageSerializerKind, storageSerializers, useLocalStorage, useSessionStorage, useStorage |
| Gzip budget | 4608 B |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/composable
Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.
import { inferStorageSerializerKind, storageSerializers, useLocalStorage, useSessionStorage, useStorage } from "@vizejs/composable/use-storage";
Try it
Remember reading progress
Persist a selected guide section in the current browser and provide an explicit way to remove the saved choice.
What to observe: Choose Components, then reload this page to keep that choice. Reset reading progress removes the saved key and restores Getting started, including after another reload.
state is writable; supported and error describe the real localStorage capability. A validator accepts only the three guide sections. writeDefaults: false leaves an absent or removed key absent. SSR uses Getting started without accessing storage; the default post-flush initial read preserves hydration before restoring a saved choice. When storage is unavailable, choices still update in this tab but reload returns to the default. Storage can fail in restricted browsers. The key belongs to this example's origin; an embedded frame may have a different origin or storage policy from its parent. Watches and storage listeners follow the component scope.
Compare the initial and interacted states


When browser storage is unavailable
Choices still change in this tab. Reloading restores Getting started because the browser cannot save the choice. This capture uses actual Chromium with localStorage disabled.

Copy the example
Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.
<script setup lang="ts">
import { computed, useId } from "vue";
import { useStorage } from "@vizejs/composable/use-storage";
type GuideSection = "getting-started" | "components" | "publishing";
const sectionId = useId();
const {
state: section,
supported,
error,
remove,
} = useStorage<GuideSection>("vize:docs:reading-progress", "getting-started", {
writeDefaults: false,
validate: (candidate): candidate is GuideSection =>
candidate === "getting-started" || candidate === "components" || candidate === "publishing",
});
const sectionTitle = computed(() => {
if (section.value === "components") return "Components";
if (section.value === "publishing") return "Publishing";
return "Getting started";
});
</script>
<template>
<div class="composable-example">
<label :for="sectionId">Guide section</label>
<select :id="sectionId" v-model="section" :aria-describedby="`${sectionId}-help`">
<option value="getting-started">Getting started</option>
<option value="components">Components</option>
<option value="publishing">Publishing</option>
</select>
<p :id="`${sectionId}-help`">
Choose where to resume reading, then reload this page. Available browser storage keeps your
choice.
</p>
<output aria-live="polite">Resume at: {{ sectionTitle }}</output>
<p role="status">
{{
error
? `Storage error: ${error.code}. The current section may not be saved.`
: supported
? "Browser storage is available."
: "Browser storage is unavailable; changes stay in this tab."
}}
</p>
<button type="button" @click="() => remove()">Reset reading progress</button>
<p>Reset removes the saved choice and restores Getting started.</p>
</div>
</template>
Runtime contract
| Utility | Category | Stability | SSR | Hydration | Cleanup | Targets | Host globals | Uses |
|---|---|---|---|---|---|---|---|---|
inferStorageSerializerKind |
storage | experimental | safe | not-applicable | none | web, server, worker, native, desktop, terminal | — | — |
useStorage |
storage | experimental | deterministic-fallback | caller-managed | reactive-scope | web, desktop | CustomEvent, window |
inferStorageSerializerKind, tryOnScopeDispose |
useLocalStorage |
storage | experimental | deterministic-fallback | caller-managed | reactive-scope | web, desktop | — | useStorage |
useSessionStorage |
storage | experimental | deterministic-fallback | caller-managed | reactive-scope | web, desktop | window |
useStorage |
API
storageSerializers
Typed built-in serializers, usable directly as the serializer option. object round-trips JSON-compatible values; map and set store their entries as JSON arrays; date stores an ISO-8601 string.
const storageSerializers: { /** Decimal `bigint` text. */ readonly bigint: StorageSerializer<bigint>; /** `"true"` / `"false"`. */ readonly boolean: StorageSerializer<boolean>; /** ISO-8601 timestamp. */ readonly date: StorageSerializer<Date>; /** JSON array of `[key, value]` entries. */ readonly map: StorageSerializer<Map<unknown, unknown>>; /** Decimal number text. */ readonly number: StorageSerializer<number>; /** JSON text. */ readonly object: StorageSerializer<unknown>; /** JSON array of members. */ readonly set: StorageSerializer<Set<unknown>>; /** Raw string. */ readonly string: StorageSerializer<string>; }
inferStorageSerializerKind
Select the built-in serializer kind for a default value. null, arrays, and plain objects use JSON ("object"); Map, Set, and Date use their dedicated kinds; primitives use their typeof. Functions and symbols cannot be stored and fall back to "string".
function inferStorageSerializerKind(value: unknown): StorageSerializerKind
useStorage
Synchronize a typed reactive value with a Web Storage compatible backend. The serializer is inferred from defaultValue (numbers stay numbers, Map/Set/Date round-trip), and every decoded value passes the validate hook before it is accepted, so corrupted or foreign data falls back to the default instead of leaking the wrong type. Writes follow the reactive value; changes from other tabs (storage events) and from other instances in the same document using the same key are applied live. Server rendering: without a browser window no storage is touched and state holds the default. Node's global localStorage is intentionally ignored because it would be shared between requests. Inside a component initialRead defaults to "post-flush", so hydrating markup that rendered the default matches before switching to the stored value. Failures (quota, disabled storage, malformed data) never throw; they are exposed through error and onError.
function useStorage<Value>( key: MaybeRefOrGetter<string>, defaultValue: MaybeRefOrGetter<Value>, options: UseStorageOptions<Value> = {}, ): StorageControls<Value>
const { state: theme } = useStorage("theme", "light" as "light" | "dark");
theme.value = "dark"; // persisted
useLocalStorage
useStorage bound to window.localStorage by default.
function useLocalStorage<Value>( key: MaybeRefOrGetter<string>, defaultValue: MaybeRefOrGetter<Value>, options: UseStorageOptions<Value> = {}, ): StorageControls<Value>
useSessionStorage
useStorage bound to window.sessionStorage by default.
function useSessionStorage<Value>( key: MaybeRefOrGetter<string>, defaultValue: MaybeRefOrGetter<Value>, options: UseStorageOptions<Value> = {}, ): StorageControls<Value>
Types
StorageLike
Minimal synchronous key-value store compatible with the Web Storage API.
| Member | Type | Description |
|---|---|---|
getItem |
(key: string) => string | null |
Read the raw string stored for key, or null when absent. |
setItem |
(key: string, value: string) => void |
Store value under key. May throw (for example on quota exhaustion). |
removeItem |
(key: string) => void |
Remove key. Removing an absent key is a no-op. |
StorageSerializer
Converts one value type to and from its stored string representation.
| Member | Type | Description |
|---|---|---|
read |
(raw: string) => Value |
Decode a stored string. Throwing marks the stored value as unreadable. |
write |
(value: Value) => string |
Encode a value for storage. |
StorageFailure
Failure observed while synchronizing a value with storage.
| Member | Type | Description |
|---|---|---|
code |
StorageErrorCode |
Which synchronization step failed. |
key |
string |
Storage key involved in the failure. |
cause |
unknown |
Exact thrown value, or the rejected candidate for "invalid-value". |
UseStorageOptions
Options for useStorage.
| Member | Type | Description |
|---|---|---|
storage? |
MaybeRefOrGetter<StorageLike | null | undefined> |
Reactive storage backend. null/undefined keeps the composable on its default value, which is also what happens during server rendering. |
eventTarget? |
MaybeRefOrGetter<EventTarget | null | undefined> |
Event target receiving cross-document storage events and same-document synchronization events between instances sharing a key. |
serializer? |
StorageSerializer<Value> |
Explicit serializer. When omitted the serializer is inferred from the default value (see inferStorageSerializerKind). |
validate? |
StorageValidator<Value> |
Schema validation hook applied to every decoded value. Rejected values fall back to the default and report an "invalid-value" failure. |
mergeDefaults? |
boolean | ((stored: Value, defaults: Value) => Value) |
Merge a stored object with the default so newly added default keys are present. true performs a shallow merge; a function merges explicitly. |
writeDefaults? |
boolean |
Persist the default value when the key is absent. |
listenToStorageChanges? |
boolean |
Follow changes made by other documents and other instances. |
deep? |
boolean |
Watch nested mutations of object values. |
flush? |
"pre" | "post" | "sync" |
Write timing relative to component rendering. |
initialRead? |
"sync" | "post-flush" |
When the stored value is first read. "post-flush" keeps the default through the first render so server-rendered markup hydrates without a mismatch, then reads storage after mounting. |
onError? |
(failure: StorageFailure) => void |
Observe read, validation, write, and removal failures. Failures never throw out of the composable. |
StorageControls
Reactive state and controls returned by useStorage.
| Member | Type | Description |
|---|---|---|
state |
Ref<Value> |
Writable value synchronized with storage. |
supported |
Readonly<Ref<boolean>> |
Whether a storage backend is currently attached. |
error |
Readonly<ShallowRef<StorageFailure | undefined>> |
Most recent synchronization failure, cleared by the next success. |
refresh |
() => void |
Re-read the stored value, replacing the reactive value. |
remove |
() => void |
Remove the key from storage and restore the default value. |