⚠️ Foldkit is in canary release for early adopters and experimenters. APIs are incomplete and may change rapidly.
Foldkit is an Elm-inspired framework for building web applications powered by Effect.
Like origami: simple parts become intricate when folded together.
Foldkit applies functional programming principles to web application development.
- Pure updates — State transitions are deterministic functions:
(model: Model, message: Message): Modelis the only way to change state. - Controlled side effects — Side effects are described as
Command<Message>values and executed by the runtime, not performed directly in update functions. - Explicit state transitions — Every state change is modeled as a specific message type and is captured in the update function.
The best way to get started with Foldkit is to use create-foldkit-app. This
will guide you through creating a new Foldkit project with your preferred
package manager and example template.
npx create-foldkit-app@latest --wizard- Counter - Simple increment/decrement with reset
- Stopwatch - Timer with start/stop/reset functionality
- Weather - HTTP requests with async state handling
- Todo - CRUD operations with localStorage persistence
- Form - Form validation with async email checking
- Routing - URL routing with parser combinators and route parameters
- Shopping Cart - Complex state management with nested models
- Snake - Classic game built with command streams
- WebSocket Chat - WebSocket integration
- Typing Game - Multiplayer typing game with Foldkit frontend and Effect RPC backend
See the full example at examples/counter/src/main.ts
import { Match as M, Schema } from 'effect'
import { Runtime } from 'foldkit'
import { Html, html } from 'foldkit/html'
import { ts } from 'foldkit/schema'
// MODEL
const Model = Schema.Number
type Model = typeof Model.Type
// MESSAGE
const Decrement = ts('Decrement')
const Increment = ts('Increment')
const Reset = ts('Reset')
const Message = Schema.Union(Decrement, Increment, Reset)
type Decrement = typeof Decrement.Type
type Increment = typeof Increment.Type
type Reset = typeof Reset.Type
export type Message = typeof Message.Type
// UPDATE
const update = (count: Model, message: Message): [Model, ReadonlyArray<Runtime.Command<Message>>] =>
M.value(message).pipe(
M.withReturnType<[Model, ReadonlyArray<Runtime.Command<Message>>]>(),
M.tagsExhaustive({
Decrement: () => [count - 1, []],
Increment: () => [count + 1, []],
Reset: () => [0, []],
}),
)
// INIT
const init: Runtime.ElementInit<Model, Message> = () => [0, []]
// VIEW
const { div, button, Class, OnClick } = html<Message>()
const view = (count: 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')], [count.toString()]),
div(
[Class('flex flex-wrap justify-center gap-4')],
[
button([OnClick(Decrement.make()), Class(buttonStyle)], ['-']),
button([OnClick(Reset.make()), Class(buttonStyle)], ['Reset']),
button([OnClick(Increment.make()), Class(buttonStyle)], ['+']),
],
),
],
)
// STYLE
const buttonStyle = 'bg-black text-white hover:bg-gray-700 px-4 py-2 transition'
// RUN
const element = Runtime.makeElement({
Model,
init,
update,
view,
container: document.getElementById('root')!,
})
Runtime.run(element)Explore the examples locally:
git clone https://github.com/devinjameson/foldkit.git
cd foldkit
pnpm install
# In one terminal - build Foldkit in watch mode
pnpm dev:core
# In another terminal - run the counter example
pnpm dev:example:counterWe're building in the open. Feedback, issues, and contributions are welcome.
- Core program loop with ADT-based update
- DOM rendering
- Optimized DOM rendering (minimal diffs, efficient updates)
- Router integration
- Devtools + tracing
MIT