Build Modern UI, Your Way
Beta is live

The most versatile web UI library.

Ilha is a tiny island architecture library that renders to plain HTML on the server and hydrates on the client with zero flicker. The core is under 2,500 lines of code— small enough to paste into any AI prompt. Routing, typed forms, and shared state are included when you need them.

npx giget@latest gh:ilhajs/ilha/templates/vite my-app
  • < 2,500 LOC core
  • Zero dependencies
  • TypeScript-first
  • No virtual DOM
  • Star on GitHub
Fully open-source
Every line is free. No paywalls, no hidden tiers.
No virtual DOM
Runs from a single import — no transform, no toolchain to wrestle with.
Works with any backend
TypeScript, PHP, Ruby, Elixir, Rust, Go — Ilha fits your stack.
Islands + signals
Plain HTML on the server, fine-grained hydration on the client.
Live demo

One island — state, events, and markup together. Edit the code and watch it run.

Why Ilha

Build modern UI without framework ceremony.

Familiar syntax, signal-driven updates, and one island for every rendering strategy — from static HTML to hydrated interactivity.

Familiar syntax

Build interactive UI without framework ceremony

Keep state, validation, events, and markup together in one tiny island.

  • Svelte-like reactivity
  • React-flavored templating
  • No framework ceremony
import ilha, { mount } from "ilha";

const Signup = ilha
  .state("email", "")
  .derived("ready", ({ state }) => state.email().includes("@"))
  .on("[data-action=join]@click", async ({ state }) => {
    await fetch("/api/waitlist", {
      method: "POST",
      body: JSON.stringify({ email: state.email() }),
    });
  })
  .render(({ state, derived }) => (
    <form class="card">
      <input name="email" bind:value={state.email} placeholder="you@company.com" />
      <button data-action="join" disabled={!derived.ready()}>
        Join waitlist
      </button>
    </form>
  ));

mount({ Signup });
Signals

Fast by default, because updates are precise

Signals track the data your UI actually reads, so updates stay focused and predictable.

  • Fine-grained updates
  • Abortable async work
  • No app-wide re-render loop
import ilha from "ilha";

const Search = ilha
  .state("query", "")
  .derived("results", async ({ state, signal }) => {
    if (!state.query()) return [];
    const res = await fetch(`/api/search?q=${encodeURIComponent(state.query())}`, { signal });
    return res.json() as Promise<string[]>;
  })
  .render(({ state, derived }) => (
    <section class="card">
      <input
        name="q"
        placeholder="Search…"
        bind:value={state.query}
      />
      <Results items={derived.results() ?? []} />
    </section>
  ));
Rendering

One island, every rendering strategy

Choose the right rendering mode per island instead of committing your whole app to one strategy.

  • Static HTML
  • Server-rendered hydration
  • Client-only islands
import { mount } from "ilha";
import { ProductCard } from "./product-card";

// Static HTML — instant first paint.
const html = ProductCard.toString({ featured: true });

// Hydrate only where you need interactivity.
const island = await ProductCard.hydratable(
  { featured: true },
  { name: "ProductCard", snapshot: true },
);

// Or mount client-side when SEO is not required.
mount({ ProductCard });
Useful extras

Routing and shared state when you need them

Start with a single import and add file-based routes or Zustand-shaped stores only when the product earns it.

  • File-based routes and dynamic pages
  • Shared cart and session state
  • Cross-island coordination
// vite.config.ts
import { pages } from "@ilha/router/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [pages()],
});

// File-based routes under src/pages/
//   index.tsx        → /
//   pricing.tsx      → /pricing
//   blog/[slug].tsx  → /blog/:slug
import { pageRouter } from "ilha:pages";
pageRouter.start();
Start a new Ilha project

Pick a template, optional project name, and copy the giget command — or open a StackBlitz sandbox.

Pick a template
Open Sandbox

Read the guide and try the tutorials.

Learn the island API, helpers, and libraries — then follow interactive counter and dex tutorials.