Skip to main content
On this pageOverview

View

Overview

The view function turns your Model into HTML. The user doesn’t see the Model directly — they see what view renders from it.

In the restaurant analogy, the waiter’s notebook says “table 3: salmon, ready.” The view is what’s actually on the table — the plate in front of the customer.

Given the same Model, view always produces the same HTML. It never modifies state directly — instead, it dispatches Messages through event handlers, feeding them back into the loop.

import { Class, Html, OnClick, button, div } from '../html'

// VIEW

const view = (model: Model): Html =>
  div(
    [Class(containerStyle)],
    [
      div(
        [Class('text-6xl font-bold text-gray-800')],
        [model.count.toString()],
      ),
      div(
        [Class('flex flex-wrap justify-center gap-4')],
        [
          // OnClick takes a Message, not a callback. The Message doesn't
          // execute anything. It just declares what should happen on click.
          // Foldkit dispatches it to your update function.
          button([OnClick(ClickedDecrement()), Class(buttonStyle)], ['-']),
          button([OnClick(ClickedReset()), Class(buttonStyle)], ['Reset']),
          button([OnClick(ClickedIncrement()), Class(buttonStyle)], ['+']),
        ],
      ),
    ],
  )

// STYLE

const containerStyle =
  'min-h-screen bg-cream flex flex-col items-center justify-center gap-6 p-6'

const buttonStyle = 'bg-black text-white hover:bg-gray-700 px-4 py-2 transition'

No hook rules

In React, functional components can hold local state and run effects via hooks, which come with ordering rules you have to follow. In Foldkit, view is guaranteed pure — no hooks, no effects, no local state. It’s a function from Model to Html.

Typed HTML Helpers

Foldkit’s HTML functions are typed to your Message type. This ensures event handlers only accept valid Messages from your application. You create these helpers by calling html<Message>() and destructuring the elements and attributes you need:

import { html } from 'foldkit/html'

// Create typed HTML helpers for your Message type.
// This ensures event handlers like OnClick only accept your Message variants.

export const {
  // Attributes
  Class,
  Id,
  Href,
  OnClick,
  OnInput,
  OnSubmit,
  Value,
  // Elements
  a,
  button,
  div,
  form,
  h1,
  input,
  p,
  span,
} = html<Message>()

This gives you strong type safety — if you try to pass an invalid Message to OnClick, TypeScript catches it at compile time. You only need to do this once per module — most apps create a single html.ts file and import from there.

Event Handling

When the customer flags the waiter, that’s a Message. In the view, event handlers work the same way — instead of imperative callbacks that modify state, you pass a Message, or a function that maps an event to a Message.

// Event handlers take Messages, not callbacks.
// When the button is clicked, Foldkit dispatches the Message
// to your update function.
button([OnClick(ClickedIncrement()), Class('button-primary')], ['Click me'])

// For input events, Foldkit extracts the value and passes it
// to your function:
input([
  OnInput(value => ChangedSearch({ text: value })),
  Value(model.searchText),
  Class('input'),
])

For simple events like clicks, you pass the Message directly. For events that carry data (like input changes), you pass a function that receives the event and returns a Message. This keeps your view declarative. It describes what Messages should be sent, not how to handle them.

So far everything has been synchronous — the user clicks a button, update produces a new Model, the view rerenders. But real apps need side effects: HTTP requests, timers, browser APIs. That’s where Commands come in.

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson