Built for Datastar

Stop shipping JavaScript.

Build interactive Datastar pages from plain TypeScript: typed data-* attributes, server-rendered TSX, signal readers, and native Response helpers.

tsx
import { Hono } from "hono"
import { post, reply } from "datastar-kit"

const app = new Hono()

let count = 0

const Count = () => <output id="count">{count}</output>

app.get("/", () =>
  reply.page(
    <main>
      <button data-on:click={post("/increment")}>Increment</button>
      <Count />
    </main>,
    { title: "Counter" }
  )
)

app.post("/increment", () => {
  count += 1
  return reply.patch(<Count />)
})

export default app

Runs where you do.

Bring any Fetch-compatible runtime.

One loop, owned by your server.

The browser handles events and patches. Your code keeps the application model.

  1. 01 · Render

    Serve the first view as HTML from backend state.

  2. 02 · Listen

    Attach Datastar attributes to ordinary elements.

  3. 03 · Handle

    Decode signals and run normal TypeScript logic.

  4. 04 · Patch

    Return HTML or signals as native Response objects.

Live · Durable Object

A counter the whole internet shares.

Tap it. The count lives in a Cloudflare Durable Object and streams to every open tab over SSE — yours and everyone else's. Open a second tab and watch it move.

live countPOST /demo/counter/bump

A kit, not a framework.

Datastar Kit owns the Datastar-shaped pieces and nothing else. Everything works anywhere a Request becomes a Response.

tsx
const todo = state({ title: "" })

const Composer = () => (
  <form
    data-signals={mod(todo.defaults, { ifMissing: true })}
    data-on:submit={mod(post("/todos"), { prevent: true })}
  >
    <input data-bind={todo.refs.title} />
    <button type="submit">Add</button>
  </form>
)

app.post("/todos", async (c) => {
  const { title } = TodoSignals.parse(await read.signals(c.req.raw))
  await db.todos.insert(title)

  return reply.stream([
    event.patch(<TodoList items={await db.todos.list()} />),
    event.signals(todo.reset())
  ])
})
Typed attributes and actions
data-on, data-bind, data-show, and friends are real TSX props.
Response helpers
reply.page, reply.patch, reply.signals, reply.stream, and reply.navigate return native Responses.
Signals at the boundary
read.signals(request) decodes Datastar payloads for your own validation layer.
Realtime in a few lines
reply.stream sends SSE patches, so live multi-tab views are an ordinary handler.
A development debugger
Drop one component into a page to inspect signals, patches, and SSE traffic.

Readable in an afternoon.

Install it, read the introduction, and ship one server-driven page.