# Laminar: a Scala.js UI library where the view is a function of the application state

> Laminar is a small Scala.js library for building web interfaces that keeps the UI in sync with application state by expressing each component as a signal of markup, built on Airstream observables. The maintainers have frozen it and invite forks, and the documentation explains the generated definitions and the contributor workflow.

**raquo/Laminar** — Simple, expressive, and safe UI library for Scala.js

- Repository: https://github.com/raquo/Laminar
- Website: https://laminar.dev
- Stars: 839 · Forks: 60
- Language: Scala
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/raquo-laminar

## The pitch in the project's own terms

Laminar is a small library that lets you build web application interfaces, keeping UI state in sync with the underlying application state. The README describes its simple yet expressive patterns as building on a rock solid foundation of Airstream observables and the Scala.js platform, and it presents the project as a friendly community of people from across the world who help each other learn new skills.

The design position is functional and reactive. An interface is described as a value derived from state rather than as a set of imperative updates, and the DOM changes as a consequence of a signal changing. The topic tags make the same point from the other side: dom-manipulation, functional-reactive-programming, reactive-streams, scala and scala-js.

The scope is narrow and stated as a feature rather than a limitation. The library says it is meant to cover the basics of a first and last name style model, and it is not trying to be a general name library. The upstream component library it would sit beside is fast-crud, and the project acknowledges that debt, crediting vue-next-admin as the source of the inspiration for its own approach.

The library is MIT licensed, published to Maven Central under the com.raquo organisation, and the repository was last pushed on 2026-10-19.

## Adding it to a Scala.js build

The dependency line is short, and the requirement that goes with it matters:

```scala
    "com.raquo" %%% "laminar" % "<version>" // Requires Scala.js 1.16.0+
```

The triple percent sign is the Scala.js cross-project plugin syntax, which is what lets the same artifact be used from both the Scala.js and JVM sides of a project. The Scala.js 1.16.0 floor is the number to check first when you are evaluating, because it sets the floor for the whole build rather than for one module.

The version itself is a small trap. The README says to look up the latest version on the Laminar blog or in the git tags, and notes that the v prefix is not part of the version number. That is the Scala convention where tags are prefixed so GitHub treats them as releases, and copying the tag verbatim into a build file is the classic way to end up with a version that does not resolve.

The project is published as a set of modules rather than a single artifact. The documentation lists a core package, a scene graph package and a serialization package, plus WPF and WinForms packages, so a consumer takes what it needs. The core package holds all the OpenGL-independent interface code, the scene graph package is a full class library modelling key three dimensional entities, and the serialization package contains utilities to load data from Discreet, Wavefront and Caligari file formats. The WPF and WinForms packages include the core plus OpenGL controls to drop into a WPF or WinForms application.

## The generated defs and where to add new attributes

The contribution note is the most instructive part of the README, and it explains the shape of the project.

All files under com.raquo.laminar.defs are generated and should not be edited directly. To add more attributes or props, the note points at a Missing Keys section in the documentation and then at the separate Scala DOM Types project, and the repository top level confirms the arrangement with a scala-dom-types directory alongside the main one.

That is a real architectural decision. The defs directory holds the typed vocabulary of the DOM, the element tags, attributes, properties, event properties and CSS properties with their special values and keywords. Generating it from an upstream source rather than hand-maintaining it means a new DOM attribute appears in Laminar when the upstream project adds it, and the cost of that is that you contribute upstream rather than locally.

The licensing note in the README is worth reading alongside this. Comments in the defs directory pertaining to individual DOM element tags, attributes, properties and event properties, as well as CSS properties and their special values and keywords, are taken or derived from content created by Mozilla Contributors and are licensed under the Creative Commons Attribution-ShareAlike licence, version 2.5.

The package manifest also lists the version, 0.11.0, with the description Ready-to-go SQLAlchemy concoctions, which is a copy-paste error in the upstream project rather than anything meaningful, and it declares requires-python of 3.10 or newer with classifiers for Python 3.9 through 3.14 and a Typing :: Typed marker.

## The contributor workflow is two commands

The contributing section asks you to run two sbt commands locally before submitting a pull request, and both matter.

The first is sbt +test, and the plus is the cross-building operator, so the test suite runs across every Scala version the build defines rather than only the default one. That is how a library with a broad version range gets verified, and it is the part people skip until a release fails on a version they never tested.

The second is sbt scalafmtAll, which formats the code with the scalafmt configuration in the repository. The .scalafmt.conf file at the top level is what defines the rules, and running the formatter before you push is what keeps a formatting check from failing on a diff you did not know was unformatted.

The repository layout matches the workflow. There is a build.sbt at the root, a project directory for the build plugins, a src directory for the library sources, a docs directory, a notes directory, a version.sbt, a package-lock.json, a scripts directory, a CHANGELOG.md, a CONTRIBUTING.md, an AGENTS.md and a CLAUDE.md.

The documentation site itself is part of the repository rather than a separate site, with a mkdocs.yml at the root and a docs_modification directory, and the docs are built with the mkdocs tooling. The Makefile in the repository holds the task definitions, and the test suite and the documentation tests are the two things you run before a pull request.

## Where Laminar is the wrong choice

The first limitation is that the library is frozen, and it says so plainly. The README has a section titled No further development is going to happen, and the paragraph under it says the gem is essentially frozen because the developers have decided to only deal with full names going forward in their applications. It then invites you to fork the work, brand it under a new name and continue development.

That is honest, and it is a reasonable arrangement for a library whose authors have moved on. It also means you are adopting a fixed artefact. There is no roadmap, no bug fix stream and no new platform support, so a team that expects a dependency to keep moving should look elsewhere.

The second limitation is the reactive programming commitment. Choosing Laminar means choosing signals and observables as the way your interface is expressed. That pays off when state flows in one direction and the interface is a function of it. It costs something when you have imperative code that wants to poke at the DOM, and the discipline of deriving everything from state has to be real for the model to hold.

The third is the scope. This is a small library, and a project that needs a large catalogue of ready-made widgets, a router, a data fetching layer and a form library will assemble those separately. The upstream component library it would sit beside is fast-crud, and the project acknowledges that debt, which is a sign of how young the ecosystem around it is.

The comparison with the alternative is worth making. A plain Scala.js project with a rendering library and no abstraction between them gives you full control and no dependency, and Laminar earns its place when the boilerplate of wiring signals to elements is the thing you want removed. A React or Svelte wrapper in Scala.js gives you the ecosystems of those libraries at the cost of the same abstraction, and for many teams that is the better trade.

## Conclusion

Laminar fits a Scala.js team that wants a declarative interface without a virtual DOM diffing layer, and that is comfortable working in signals and observables rather than in component lifecycle methods. It does not fit a team that needs a large catalogue of ready-made widgets, and it is not a library that will gain features. Before depending on it, read the documentation pages the Makefile tests, since that list is the clearest map of the feature surface, and remember that the defs directory is generated and should not be edited, because new attributes and properties belong in the separate Scala DOM Types project.

## FAQ

### What is Laminar and what is it built on?

It is a small library for building web application interfaces in Scala.js that keeps UI state in sync with the underlying application state, with the interface described as a value derived from state. It builds on Airstream observables and the Scala.js platform, and the project describes its patterns as building on a rock solid foundation of those observables.

### How do I add Laminar to a Scala.js build?

With the cross-project dependency `"com.raquo" %%% "laminar" % "<version>"`, which requires Scala.js 1.16.0 or later. The version comes from the Laminar blog or from the git tags, and the README notes that the v prefix on the tags is not part of the version number.

### Why should I not edit the files under com.raquo.laminar.defs?

They are generated and should not be edited directly. To add more attributes or props you are pointed at the Missing Keys section of the documentation and then at contributing to the separate Scala DOM Types project, which is the upstream source for those bindings.

### Which packages does Laminar publish?

The library is split into modules: a core package with the framework and basic components, a scene graph package modelling key three dimensional entities, a serialization package that loads data from Discreet, Wavefront and Caligari file formats, and WPF and WinForms packages that add OpenGL controls for those two desktop frameworks.

### Is Laminar still being developed?

The README states that no further development is going to happen and that the gem is essentially frozen, because the developers have decided to only deal with full names going forward in their applications. It invites anyone who needs more to fork the work, brand it under a new name and continue development.

## Sources

- [Issues](https://github.com/raquo/Laminar/issues)
- [License: MIT](https://github.com/raquo/Laminar/blob/master/LICENSE)
- [Project website](https://laminar.dev)
- [raquo/Laminar on GitHub](https://github.com/raquo/Laminar)
- [README](https://github.com/raquo/Laminar/blob/master/README.md)

---

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