npx claudepluginhub foldkit/foldkitSkills for building Foldkit apps — app generator, message scaffolding, submodel extraction, and architecture auditing
Share bugs, ideas, or general feedback.
Documentation · Examples · Getting Started
Foldkit is a frontend framework for TypeScript, built on Effect, using The Elm Architecture. One Model, one update function, one way to do things. No hooks, no local state, no hidden mutations.
[!NOTE] Foldkit is pre-1.0. The core API is stable, but breaking changes may occur in minor releases. See the changelog for details.
Foldkit is for developers who want their architecture to prevent bugs, not just catch them. If you want a single pattern that scales from a counter to a multiplayer game without complexity creep, this is it.
It's not incremental. There's no React interop, no escape hatch from Effect, no way to "just use hooks for this one part." You're all in or you're not.
Every Foldkit application is an Effect program. Your Model is a Schema. Side effects are values you return, not callbacks you fire — the runtime handles when and how. If you already know Effect, Foldkit feels natural. If you're new to Effect, Foldkit is a great way to immerse yourself in it.
create-foldkit-app is the recommended way to start a new project. It scaffolds a complete setup with Tailwind, TypeScript, ESLint, Prettier, and the Vite plugin for state-preserving HMR — and lets you choose from a set of examples as your starting point.
npx create-foldkit-app@latest --wizard
This is a complete Foldkit program. State lives in a single Model. Events become Messages. A pure function handles every transition.
import { Match as M, Schema as S } from 'effect'
import { Command, Runtime } from 'foldkit'
import { Html, html } from 'foldkit/html'
import { m } from 'foldkit/message'
// MODEL
const Model = S.Struct({ count: S.Number })
type Model = typeof Model.Type
// MESSAGE
const ClickedDecrement = m('ClickedDecrement')
const ClickedIncrement = m('ClickedIncrement')
const ClickedReset = m('ClickedReset')
const Message = S.Union(ClickedDecrement, ClickedIncrement, ClickedReset)
type Message = typeof Message.Type
// UPDATE
const update = (
model: Model,
message: Message,
): [Model, ReadonlyArray<Command.Command<Message>>] =>
M.value(message).pipe(
M.withReturnType<[Model, ReadonlyArray<Command.Command<Message>>]>(),
M.tagsExhaustive({
ClickedDecrement: () => [{ count: model.count - 1 }, []],
ClickedIncrement: () => [{ count: model.count + 1 }, []],
ClickedReset: () => [{ count: 0 }, []],
}),
)
// INIT
const init: Runtime.ProgramInit<Model, Message> = () => [{ count: 0 }, []]
// VIEW
const { div, button, Class, OnClick } = html<Message>()
const view = (model: Model): Html =>
div(
[
Class(
'min-h-screen bg-white flex flex-col items-center justify-center gap-6 p-6',
),
],
[
div(
[Class('text-6xl font-bold text-gray-800')],
[model.count.toString()],
),
div(
[Class('flex flex-wrap justify-center gap-4')],
[
button([OnClick(ClickedDecrement()), Class(buttonStyle)], ['-']),
button([OnClick(ClickedReset()), Class(buttonStyle)], ['Reset']),
button([OnClick(ClickedIncrement()), Class(buttonStyle)], ['+']),
],
),
],
)
// STYLE
const buttonStyle = 'bg-black text-white hover:bg-gray-700 px-4 py-2 transition'
// RUN
const program = Runtime.makeProgram({
Model,
init,
update,
view,
container: document.getElementById('root')!,
})
Runtime.run(program)
Source: examples/counter/src/main.ts
Foldkit is a complete system, not a collection of libraries you stitch together.