# Mint: a compile-to-JavaScript front-end language written in Crystal

> Mint is a component-oriented language that compiles to JavaScript, with a compiler written in Crystal. It targets developers who want typed state, a built-in virtual DOM and a single toolchain for building a front-end, and it asks them to accept a pre-1.0 language to get that.

**mint-lang/mint** — 🍃 A refreshing programming language for the front-end web.

- Repository: https://github.com/mint-lang/mint
- Website: https://mint-lang.com
- Stars: 4,267 · Forks: 153
- Language: Crystal
- License: BSD-3-Clause
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/mint-lang-mint

## The problem Mint solves, and who ends up using it

A front-end built with Mint does not start from a framework you assemble out of npm packages. The language ships the pieces itself: components, state, a virtual DOM and a router, all compiled ahead of time to JavaScript. The README's status section describes the project as "in development, converging on 1.0", with syntax and standard library "mostly stable". That sentence is the honest summary of the trade. You get one coherent toolchain and a compiler that reads your whole program, and in exchange you accept that the language has not frozen.

The audience is narrow but real. It suits a developer who already writes typed code, wants state changes to be explicit, and is willing to learn a syntax that is neither JavaScript nor TypeScript. It does not suit a team that needs to hire from a large pool, because the language is its own thing and the README points to Discord and Awesome Mint for community material rather than to a broad ecosystem. The projects the README lists as showcases (the Mint website, Mint Realworld, Mint UI) are all maintained by the language's own authors, which tells you the ecosystem is young.

## How the compiler and runtime fit together

The repository is split into three directories that map to the pipeline. `src/` holds the Crystal compiler. `core/` holds the language's own standard library and tests, written in Mint. `runtime/` holds the JavaScript runtime that compiled output calls into. The Makefile makes the dependency explicit: the `bin/mint` target depends on files under `core/source`, `runtime/src`, `src`, and on two generated assets, `src/assets/runtime.js` and `src/assets/runtime_test.js`. Those two files are themselves built by running `make index` inside `runtime/`, so the runtime is bundled into the compiler binary before the compiler is built.

That layout explains the deployment story. The Dockerfile builds a static binary with `shards build mint --static --no-debug --release` on a Crystal 1.19.1 Alpine image, then copies only `/bin/mint` into a bare Alpine image. The final image installs imagemagick, pngcrush and optipng, which the Dockerfile comments tie to favicon generation, plus git for installing dependencies. The entrypoint is the compiler, and the default command is `start`, so the container runs the Mint development server. Nothing about a Node.js runtime appears in the image; the compiler is a single self-contained executable.

State in a component is declared and changed through `next`. The README's counter example declares `state counter = 0`, then defines `fun increment { next { counter: counter + 1 } }` and renders two buttons bound with `onClick`. There is no separate store library to wire up.

## Installing Mint and running a first component

The README does not inline installation steps; it links to https://mint-lang.com/install, and that page is the place to get the binary for your platform. What the repository does show is how the compiler is built from source. If you have Crystal and shards available, the Makefile target is the supported path:

```bash
make build
```

That produces `bin/mint`. The `local` target moves it into `~/.bin/mint`:

```bash
make local
```

If you would rather not install Crystal, the Dockerfile builds a self-contained image. The repository's docker-compose.yml defines an `app` service on the `crystallang/crystal:1.19.1-alpine` image with the repository mounted at `/app`:

```yaml
version: "2"
services:
  app:
    image: crystallang/crystal:1.19.1-alpine
    working_dir: /app
    volumes:
      - .:/app
```

Once the binary is on your path, the README's own example is the smallest useful program. Save it in a file and compile it:

```mint
component Counter {
  state counter = 0

  fun increment { next { counter: counter + 1 } }
  fun decrement { next { counter: counter - 1 } }

  fun render {
    <div>
      <button onClick={decrement}>"Decrement"</button>
      <span>counter</span>
      <button onClick={increment}>"Increment"</button>
    </div>
  }
}
```

The Dockerfile's default command is `start`, which is the development server. What you should see is a page with two buttons and a number between them, and the number changing when you click. If the compiler reports an error instead, the error comes from the Crystal compiler checking your Mint source, not from a browser.

## Where Mint is the wrong choice

The clearest limitation is stated by the project itself: it is pre-1.0. "The syntax and and standard library are mostly stable" is not the same as frozen, and a language that is still being refactored for stability can change under you between releases. The release history backs this up: 0.28.0 and 0.28.1 landed on 2025-12-01 and 2025-12-02, and 0.29.0 followed on 2026-06-17. If your project needs a syntax guarantee you can point at in a contract, Mint does not offer one yet.

The second limitation is the ecosystem. The README's contribution list includes "create modules for not yet implemented Web APIs" and notes that modules like `String` and `Dom` are missing features. That is a direct statement that the standard library has gaps. You will hit a Web API that Mint does not wrap, and your options are to write the wrapper yourself or to drop into JavaScript interop, which the README does not document in the text provided here.

The third is tooling. The Makefile shows `mint test -b chrome` for running the core tests, so browser-based testing exists, but the README does not describe a test runner, a dev-server flag list, or a bundler configuration. If your team depends on a mature plugin ecosystem for linting, bundling and CI, you will be building that yourself.

## How Mint differs from Elm and from Svelte

The closest comparison is Elm, and the difference is in the compiler. Elm's compiler is written in Haskell and produces JavaScript with a strict, no-runtime-escape architecture; Mint's compiler is written in Crystal, and the repository ships a JavaScript runtime under `runtime/` that the compiled output calls into. That means Mint's output is not standalone in the way Elm's is; the runtime is part of the contract between compiler and generated code, which is why the Makefile rebuilds `src/assets/runtime.js` whenever `runtime/src` changes.

The comparison with Svelte is about when work happens. Svelte compiles components to code that mutates the DOM directly. Mint's README example uses `next` to update state and a `render` function that returns markup, which is the virtual-DOM model. The practical consequence is that Mint carries a runtime and does a diff at update time, while Svelte's compiled output does not. If bundle size on a constrained device is your main constraint, that difference matters more than syntax.

A third option is to stay in TypeScript and pick a framework. That keeps you inside npm, which Mint deliberately leaves. The reason to leave is the one the README gives: a compiler that sees the whole program and a standard library that ships with the language, rather than a set of packages you keep in sync.

## Maintenance, licensing and what an upgrade costs

The repository is not archived, and the last push was on 2026-09-11. The most recent release, 0.29.0, is dated 2026-06-17. The README describes the project as converging on 1.0 and lists polishing and refactoring as the current work, so the release cadence is the thing to watch: 0.28.0 and 0.28.1 came one day apart in December 2025, then 0.29.0 arrived roughly six months later. A patch release immediately after a minor is normal; a six-month gap before the next minor is the pattern you should plan around.

The upgrade cost is tied to the pre-1.0 status. Because the syntax and standard library are "mostly stable" rather than frozen, you should read the release notes for each minor before bumping, and pin the version you build against. The repository gives you the tools to do that: `shard.lock` pins the Crystal dependencies of the compiler itself, and the Dockerfile pins Crystal to 1.19.1-alpine, with a comment in docker-compose.yml telling you to keep that version in sync with `.tool-versions` and the Dockerfile. Do the same for the Mint version you depend on.

Mint is licensed under BSD-3-Clause. That is a permissive licence, and it is the same family used by many compilers and runtimes. It does not impose a copyleft obligation on your application. This is a description of the licence identifier in the repository, not legal advice; if your organisation has a licence-review process, run it.

## Conclusion

Adopt Mint if you are building a small to medium front-end, you are comfortable reading the reference at mint-lang.com/reference, and you accept a language that says it is converging on 1.0. Do not adopt it if you need a stable syntax guarantee, a large hiring pool, or a package ecosystem comparable to npm. Before committing, run `mint format` and `mint test -b chrome` against a throwaway component to confirm the toolchain behaves on your machine, and check the 0.29.0 release notes for changes since the version you plan to pin.

## FAQ

### What is the Mint programming language?

Mint is a programming language for the front-end web that compiles to JavaScript. Its compiler is written in Crystal, and the repository also ships a JavaScript runtime that compiled output calls into.

### How do I install Mint?

The README links to https://mint-lang.com/install for installation. From the repository you can also build the compiler with `make build`, which produces `bin/mint`, or use the Dockerfile, whose entrypoint is the mint binary and whose default command is `start`.

### What does the Mint counter example look like?

The README shows a `Counter` component with `state counter = 0`, two functions that call `next` to increment and decrement, and a `render` function returning two buttons and a `span` that displays the counter.

### Is Mint stable enough for production?

The README states the project is in development and converging on 1.0, with syntax and standard library "mostly stable". That is not a freeze, so you should pin a version and read release notes before upgrading.

## Sources

- [License: BSD-3-Clause](https://github.com/mint-lang/mint/blob/master/LICENSE)
- [mint-lang/mint on GitHub](https://github.com/mint-lang/mint)
- [Project website](https://mint-lang.com)
- [README](https://github.com/mint-lang/mint/blob/master/README.md)
- [Releases](https://github.com/mint-lang/mint/releases)

---

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