Framework
built to
disappear.

Development stays ▌

Create sites that are the target of envy.
Fast, reactive, feature-rich and most importantly, safe.
All with a runtime smaller than a JPEG.

25.8xfaster than Next.JS
5KB runtime
0browser deps
MITlicense

raw API route · identical payloads · oha, 50 connections*

The best of
every framework.

Routing, state, server code, rendering, images — the ideas behind React, Solid, Astro and SvelteKit, as they work in Elegance.

File-system routing
pages/
pages/
├── page.ts               →  /
├── about/page.ts         →  /about
├── blog/
│   ├── page.ts           →  /blog
│   └── [slug]/page.ts    →  /blog/:slug
└── api/users/page.ts     →  /api/users

The build scans pages/ and generates the route
table from the tree above. Renaming a folder
renames the route; deleting a file removes it.
[slug] folders match any single segment —
feature 07 covers them.

What it actually does.

A small footprint and no dependencies doesn't mean a small feature set. With Elegance, the opposite is true. Here are just a fraction.

01

FS Routing

Your page folder IS your router. A directory with a page.ts inside is a route, full stop. Just export a default function that resolves into some element calls.

/[slug] · /[...rest] · layouts · API routes
02

Atoms

State lives in atoms. Read one inside view() and just that component re-renders when the atom changes. Just a value with a .value.

Fine-grained · no vdom · auto-batched
03

Write Server Code Anywhere

Our state-of-the-art compiler let's you write server-side code worry-free in the same file as your client-facing code. Import Chart in the same place you make your DB calls, with no worries.

SSR by default · !no-bundle · dead code elimination
04

Static or live, per page

Build output is static HTML by default. Fast, cacheable and free to host anywhere. One export flips a page to build per-request. Mix them freely across the same project; your blog can be static while your dashboard is live.

export const isDynamic = true
05

Atom hydration

Whatever state your server computed gets serialized into the page and restored on the client automatically. No need to re-fetch data you already have.

SSR state → client · zero wiring
06

Nested layouts

Drop a layout.ts into any directory and it wraps every route beneath it. Layouts, just like pages, can be dynamic or static; you can even mix and match. They render outer to inner.

Nested · independent · per-layout isDynamic
07

API routes

Export GET, POST, PUT, DELETE, or PATCH from any route.ts and that route handles the request as a plain HTTP handler.

GET · POST · PUT · DELETE · PATCH
08

Slug routes

Name a directory [id] and the segment becomes a typed parameter. [...rest] catches everything that follows. Slug values are available as props inside the page constructor in that route.

/posts/[slug] · /files/[...path]
09

Middleware

Run code before any route resolves. Auth checks, redirects, request rewriting, header injection. Middleware composes cleanly and can short-circuit the response without touching the page at all.

Auth · redirects · headers · rewriting

Three ideas.
One framework.

The entire mental model. Here's all of it.

Drop a directory into pages/ with a page.ts inside. That's a route! No config, no imports, no registration step. The filesystem is the router.

Layouts stack from layout.ts files walking up the directory tree, outermost-first. Each one can independently opt into server rendering. You never touch a router config.

concept 01 | Routes
pages/
├── page.ts            →  /
├── layout.ts          →  wraps all routes below
├── about/
│   └── page.ts        →  /about
├── blog/
│   ├── page.ts        →  /blog
│   ├── layout.ts      →  wraps /blog/* only
│   └── [slug]/
│       └── page.ts    →  /blog/:slug
└── api/users/
    └── page.ts        →  /api/users  (HTTP handlers)

Prefer JSX syntax?
Use TSX.

Elegance supports both .ts and .tsx as first-class page formats. Tag functions JSX syntax get the same atoms, same routing and identical output.

Either way, you get full TypeScript inference end-to-end: props, atoms, and return types are all checked at compile time.

.ts.tsxidentical output · fully typed
page.ts
const Card = component<{ name: string }>({
  atoms: { open: false },
  view: (self, { open }) =>
    article({ class: "card" },
      h2({}, self.props.name),
      open.value && p({}, "Details…"),
      button({
        onclick: () =>
          open.value = !open.value
      }, open.value ? "Close ↑" : "Open ↓")
    )
});
or
page.tsx
const Card = component<{ name: string }>({
  atoms: { open: false },
  view: (self, { open }) => (
    <article class="card">
      <h2>{self.props.name}</h2>
      {open.value && <p>Details…</p>}
      <button
        onclick={() =>
          open.value = !open.value}
      >
        {open.value ? "Close ↑" : "Open ↓"}
      </button>
    </article>
  )
});

Built for
absurd speed.

Stress-tested against Next.JS on a page with 200,000 mounted components.

Elegance
68,877req/srequests per second
0.72msaverage latency
1.49msp99 latency
198Btransferred · Brotli
Next.JS
15req/srequests per second
3.45saverage latency
3.85sp99 latency
1.07MBtransferred · GZip
vs
4,592x more requests/sec · 200K <p> elements
Full methodology & raw results →

Small surface.
Real projects.

Every concept above expressed as actual files you'd ship.

pages/blog/page.ts
// pages/blog/page.ts
// The directory name is the route. No config, no registration.

const PostList = component({
  atoms: {
    posts: [] as Post[],
    loading: true,
  },

  // init runs on the server, ideal for data that should be
  // present before the first byte of HTML is sent
  init: async (_, { posts, loading }) => {
    posts.value = await db.query(
      "SELECT * FROM posts ORDER BY created_at DESC"
    );
    loading.value = false;
  },

  view: (_, { posts, loading }) =>
    loading.value
      ? div({ class: "skeleton" })
      : ul({ class: "post-list" },
          ...posts.value.map(post =>
            li({},
              Link({ href: `/blog/${post.slug}` }, post.title),
              time({ datetime: post.date }, formatDate(post.date))
            )
          )
        )
});

export default function BlogIndex() {
  return PostList();
}
01
Transparent preprocessing

OXC rewrites tag calls and assigns atom IDs in one fast pass. Nothing changes about how you write TypeScript, the transformation is an implementation detail, not a mental model you carry around.

02
Atom hydration, zero wiring

Atom values are serialized to JSON during SSR and restored on the client automatically. No manual hydration calls, no mismatch warnings, no extra round-trip for state you already fetched.

03
init server-side, onMount client-side

init is awaited during SSR, the right place for data fetching. onMount fires after the DOM is live. onUnmount fires on teardown. The full lifecycle fits on an index card.

Two commands,
et Voilà.

Write a page.ts. Export a default function. Elegance handles the rest.

$ npx create-elegance-app
$ npx elegance          # dev server, hot-reload

no black box required!