# lustre: the flagship example stops one bracket short, and the headings carry docs-generator anchors

> A Gleam framework for building HTML, single page applications and server components, published on Hex and maintained by one person around two jobs. The README is enthusiastic and opinionated, and it has two copy-paste bugs worth knowing about before you follow it.

**lustre-labs/lustre** — A Gleam web framework for building HTML templates, single page applications, and real-time server components.

- Repository: https://github.com/lustre-labs/lustre
- Website: https://hexdocs.pm/lustre
- Stars: 2,447 · Forks: 156
- Language: Gleam
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/lustre-labs-lustre

## The flagship example stops one bracket short of compiling

The README presents one code sample and introduces it as what the library looks like. It ends here:

```gleam
    button([on_click(Decr)], [text(" - ")]
```

The decrement button is missing its closing square bracket and parenthesis, and the `view` function above it is never closed. As printed the sample does not parse, which is worth knowing before you paste it.

What the sample is meant to show is still legible, and it is a compact summary of the whole API. Five imports cover the surface: `gleam/int`, `lustre`, `text` from the element module, `div`, `button` and `p` from the HTML module, and `on_click` from the event module. The application is built with a three-call shape, where a `simple` call takes the init, update and view functions, and a `start` call takes that application, a selector string for the mount point, and `Nil` for flags.

State is an integer, the message type has two variants, and the view renders the count between two buttons. There is no template file, no build step and no generated code in the example at all, which is the point the framework is making.

## Every heading carries a {#anchor} that GitHub renders as visible text

The section headings are written with explicit anchors:

```markdown
## Features {#features}
```

That is mdBook syntax, not GitHub Markdown, and there are six of them, one per section, each matched by a link in the table of contents above. Rendered on the repository, the heading reads as the words Features followed by a brace and the word features. The anchor links in the table of contents point at fragments that do not exist on that page, so they do nothing there.

The anchors are not a mistake so much as a leak. They are there so the HexDocs build, which is where the homepage field points, can address individual sections, and the README is written in a dialect that happens to be valid in both places.

Two smaller pieces of the same kind sit at the top. The two badge anchors for the Hex package and the documentation carry no alt text, so they render as two empty links. And the website link is not merely unused, it is commented out inside an HTML comment with its separator span left behind in the file, so the navigation row has a stray pipe in it that no longer leads anywhere.

## The promoted feature is the one the philosophy says not to reach for

The feature list leads with universal components: write once, run anywhere, an Elm meets Phoenix LiveView framing. Then the philosophy section argues against them.

The text says Lustre encourages simple approaches to constructing views over complex ones, and that the library does have a way to create encapsulated stateful components, something it says was sorely missed in Elm, but that it should not be the default. Prefer simple functions to stateful components.

That is an unusual thing for a framework README to say about its own headline capability, and it is the most useful paragraph in the file. It tells you the intended shape of an application: plain functions producing elements, with components reserved for the cases that need them.

The rest of the philosophy section is the argument itself. Modern frontend development is hard, some of that complexity is necessary, and a lot of it is accidental or comes from having too many options. Where possible there should be only one way to do things, which is stated as the same design philosophy Gleam itself has. That is why one state management system ships out of the box, modelled after Elm and Erlang/OTP.

## Server components mean a DOM has to exist on the server

The three destinations for a component are spelled out: run inside an existing Lustre application, export as a standalone Web Component, or run on the server with a minimal runtime for patching the DOM.

That third one is the expensive option and the file does not price it. Patching a DOM on a server means the server process needs a DOM implementation in memory, and the cost of that implementation, in memory and in latency per rendered page, is not mentioned anywhere in the README.

The mechanism behind all three is described as Gleam's multiple targets, so one component source is compiled to more than one destination rather than being duplicated. That is the technically interesting claim and the reason a component written once can land in a browser, as a custom element, or on a server that never sends JavaScript for that part of the page.

The real-time server component part of the repository description is the same idea aimed at the server-rendering use case, and it is why the framework is described as building HTML templates, single page applications and real-time server components, which is three products rather than one.

## No templates, and the same word means two different things on one page

One feature bullet reads: a declarative, functional API for constructing HTML, with no templates and no macros, just Gleam.

The repository description says the framework is for building HTML templates. Another bullet promises server-side rendering for static HTML templating. So templates appear three times on the page, and the claim that there are none is about string templates and macro-based templating languages, not about producing templated HTML.

That is a defensible distinction and a reader will probably work it out, but the headline wording makes the first encounter confusing rather than clear.

The rest of the feature list is equally specific about what is borrowed. State management follows an Erlang and Elm-inspired architecture, side effects are managed rather than free-floating so that code is predictable and testable, and the CLI is described as batteries-included. The `dev/` directory at the root of the repository holds the tooling package, which is installed separately and as a development dependency.

## Over 20 examples, a handful of small applications, and six directories

The same directory gets three different descriptions in one file.

The example section says Lustre comes with over 20 examples. The closing section says the examples directory contains a handful of small applications that demonstrate different aspects of the library. And the tree at the root of the repository has six numbered directories under `examples/`: basics, inputs, effects, applications, components and server components.

Six folders can certainly hold more than twenty applications, so none of the three is necessarily wrong. What is odd is that a project whose selling point is that there is one way to do things cannot agree on how many examples it has.

The numbering is worth a second look because it is a reading order, and it is not the order the framework describes. Basics and inputs come first, then effects, then applications, then components, and server components last. So the two features the philosophy section discourages, components and server components, are the final two stops.

There is also a `birdie_snapshots/` directory at the root, which is where Gleam's snapshot testing library keeps its committed golden files. That is a real testing commitment, and one that puts the burden of reviewing binary-looking diffs on whoever reviews a pull request.

## One maintainer, a separate dev tools package, and five months without a tag

The support section says Lustre is mostly built by just me, around two jobs, and links to a GitHub sponsorship page. It is the plainest governance statement in the file and worth reading as a fact rather than a modesty exercise: the bus factor is one, and the person says so.

Installation is two commands, and the second one is the tooling:

```sh
gleam add lustre
gleam add --dev lustre_dev_tools
```

So the batteries-included CLI is a separate Hex package pulled in with a development flag, and the `dev/` directory at the root is its source. Two published packages, one for the library and one for the build experience, which is a reasonable split and one more thing to track when you pin versions.

The release history runs v5.5.2 on 14 January 2026, v5.6.0 on 16 February and v5.7.0 on 6 May. The default branch was then pushed on 2 October 2026, so five months of work sit past the newest tag. On a project this size that is the normal consequence of one maintainer, and it is the single most useful thing to know before depending on it.

## Conclusion

Judgment: the design here is coherent and the reasoning is published, which is rarer than it should be. The philosophy section states the trade-offs in plain language, including one against its own headline feature, and the state model borrows from two languages whose designers already argued about it. For a Gleam shop wanting server-rendered HTML and a single state system without a second language in the stack, that is a real option. Three things to check first. The README's own example does not compile as printed, so read the rendered examples rather than copying the block. Every heading carries an mdBook-style anchor that renders as literal text on GitHub, so the table of contents links will not resolve there. And the newest tag is five months older than the last push, on a project whose entire maintenance capacity is one person, so pin a version and read the changelog. The `pages/` directory at the root is unexplained in the file.

## FAQ

### What is Lustre and what can I build with it?

Lustre is a Gleam framework for building HTML templates, single page applications, Web Components and real-time server components. It offers a declarative functional API for constructing HTML with no string templates and no macros, state management modelled on Elm and Erlang/OTP, managed side effects, and server-side rendering for static templating. It is published on Hex and is MIT licensed.

### How do I install Lustre in a Gleam project?

Add the library with `gleam add lustre`, and optionally the companion development tooling with `gleam add --dev lustre_dev_tools`. Documentation and the API reference are on HexDocs at hexdocs.pm/lustre, which also carries a quickstart guide and a reference page for the examples.

### Who maintains Lustre?

The README states that Lustre is mostly built by one person, Hayleigh Thompson, around two jobs, and offers a GitHub sponsorship link. Contributions are invited through issues and pull requests. The default branch was last pushed on 2 October 2026, and the most recent release is v5.7.0 from 6 May 2026.

## Sources

- [License: MIT](https://github.com/lustre-labs/lustre/blob/main/LICENSE)
- [lustre-labs/lustre on GitHub](https://github.com/lustre-labs/lustre)
- [Project website](https://hexdocs.pm/lustre)
- [README](https://github.com/lustre-labs/lustre/blob/main/README.md)
- [Releases](https://github.com/lustre-labs/lustre/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lustre-labs-lustre
