A small system for thinking clearly about UI — and a runtime that stays out of the way.
Glint is a tiny, signal-driven runtime for authoring native Web Components using real HTML templates and fine-grained reactivity — with no build step, no virtual DOM, and no hidden machinery.
No JSX.
No compiler.
No re-rendering theater.
Just components whose identity and behavior are explicit.
Most UI complexity is accidental — and most bugs are identity bugs.
Glint exists to explore a simpler question:
What does UI look like when identity is explicit and change is localized?
Instead of abstracting the platform away, Glint stays close to it — making state, dependencies, and updates visible and easy to reason about.
Glint is both:
- a practical way to write Web Components, and
- a thinking tool for understanding UI systems.
import { define, html } from 'glintjs';
define('my-counter', ({ state }) => {
const count = state.signal(0);
const doubled = state.computed(() => count() * 2);
return html`
<button onclick=${() => count(count() + 1)}>
Count: ${count} (x2 = ${doubled})
</button>
`;
});That’s the entire model.
When count changes, only the DOM nodes that depend on it update.
No hooks.
No lifecycle choreography.
No re-render cycles.
Each component maps directly to a native Custom Element.
No virtual tree.
No reconciliation.
No pretending to be the DOM.
Glint uses tagged template literals:
html`
<h2>${title}</h2>
<p>${description}</p>
`Expressions can be signals, functions, arrays, or nested templates.
The runtime connects dependencies and steps aside.
const count = ctx.state.signal(0);Signals are stable, explicit references:
- call with no args → read
- call with a value → write
count(); // read
count(10); // writeNo proxies.
No implicit tracking rules.
If something matters over time, it has a signal.
${each(items, item => html`<li>${item}</li>`)}
${when(loading, () => html`<p>Loading…</p>`)}No syntax.
No magic.
Just composition.
Glint does not have a “root component” or a render loop.
It does not run your application. It does not re-invoke render functions. It does not own your lifecycle.
render places DOM into the world — once — and steps aside.
import { render, html } from 'glintjs';
render('#app', html`
<h1>Hello</h1>
<my-counter></my-counter>
`);targetis a DOM node or selectortemplateis a Glint template
That’s it.
After this call:
- the DOM exists
- Custom Elements instantiate naturally
- signals wire themselves to the nodes that depend on them
- updates happen locally and automatically
There is no global render cycle.
Earlier versions allowed this pattern:
render('#app', () => html`
<h1>Hello</h1>
<my-counter></my-counter>
`);This has been deprecated.
Passing a function implies:
- a render phase
- a re-invocable root
- framework ownership of application flow
Glint does not work that way.
There is no top-level re-render. There is no special “root” execution context. There is no framework-managed lifecycle.
Accepting a function would suggest behavior that does not exist.
Think of render as placement, not execution.
You are saying:
“Put this DOM here.”
Not:
“Run my app.”
Once placed:
- the browser manages element lifecycles
- signals manage change
- identity lives where it belongs: in the DOM
Glint stays out of the way.
You don’t need render at all if you don’t want it.
Because Glint is built on native Web Components, you can also:
- author components
- import them
- use them directly in HTML
render exists purely as a convenience — not as a governing abstraction.
renderruns once- it places a template into a container
- it does not imply re-rendering
- it does not define application structure
- it does not own lifecycle or flow
Glint enables applications without prescribing them.
Glint is built around a few constraints:
- Clarity over cleverness
- Explicit identity
- Localized change
- Minimal surface area
- The platform is the foundation
Or more simply:
Glint prefers being understandable to being impressive.
You can use it — or just read it... Or don't! 😉
Glint is not:
- a React alternative (in the best of ways)
- a virtual DOM framework
- a compiler
- an application governor; it's a UI substrate.
- an attempt to “win” the ecosystem
It’s a reference implementation of a way of thinking about UI — usable as a runtime, valuable as a lens.
Glint is stable enough to explore and reason with, but not yet recommended for production use yet:
- Breaking API changes could happen as glint is an evolving experiment
- continuation of first point: README docs may be out of sync with some branches' APIs
The smallness is intentional.
MIT
Do whatever you want. Just don’t pretend it’s something it isn’t.