v4 →Now with React Native, Svelte, and Vue adapters

The state library
that knows when to forget()

tact is a tiny, persisted-by-default, schema-validated state library. 1.4kb gzipped, zero dependencies, perfect TypeScript inference, and an opinion about when to wipe stale state.

pnpmnpmbunyarn
pnpm add tact
✓ MIT · 1.4kb gzipped · ✓ zero deps · ✓ 100% type-safe · ✓ React, RN, Svelte, Vue, Solid · ✓ 14ms first paint
store.tsApp.tsx
// 1. Define a store · plain TypeScript
import { defineStore, z } from "tact"

export const useCart = defineStore({
  name: "cart",
  schema: z.object({
    items: z.array(z.object({
      id: z.string(),
      qty: z.number().int().positive(),
    })),
    coupon: z.string().nullable(),
  }),
  initial: () => ({ items: [], coupon: null }),
  // auto-forget when stale
  forget: { after: "30d", when: "signedOut" }
})
store.tsApp.tsx
// 2. Use it · React, full inference
import { useCart } from "./store"

export function Cart() {
  const { items, addItem, forget } = useCart()

  return (
    <div>
      <h2>{items.length} item(s)</h2>
      <button onClick={() => addItem({
        id: "abc", qty: 1
      })}>Add</button>
      {/* opt-out: wipe locally + remotely */}
      <button onClick={forget}>Forget</button>
    </div>
  )
}
// FEATURES

Six reasons it stays tiny.

tact was written to replace the 14 utility hooks every team rewrites. Schema-first, persisted-by-default, never-grows-stale.

1.4kb gzipped · zero deps

Smaller than your shadcn button. The 4 kb you save translates into ~14 ms of TTI on a mid-tier Android.

🧬

Schema-validated by default

Pass a z.object(). We validate on hydrate, on write, and on every IPC message. Bad data fails loudly.

🪟

Persisted-by-default

Stores hydrate from localStorage / AsyncStorage / cookies depending on platform. Opt out per-store with persist: false.

🧹

Knows when to forget()

Auto-wipe stale state with forget: { after: "30d", when: "signedOut" }. The library most others forgot to ship.

🔄

Realtime sync optional

Add sync: "supabase" or your own adapter. Multi-tab + multi-device with conflict-resolution baked in.

🌐

Universal · 6 frameworks

React, RN, Svelte, Vue, Solid, vanilla. One defineStore API. Stores are framework-portable: same source, swap the hook.

18.4k
GitHub stars · since v1
1.4kb
Gzipped · zero deps
94%
Test coverage · CI gates at 90
214k
Weekly downloads · NPM
// COMMUNITY

Built in the open.

GitHub
18.4k stars · 420 PRs merged
💬
Discord
2,400 members · 14 maintainers
📕
Docs
14 guides · 32 examples
🐦
X / Twitter
@tact_dev · 12k followers

Try it. It's 1.4kb.

If it doesn't feel obviously smaller than what you have, throw it out. We won't be mad. (We will be a little sad.)