Sitemap
Business4s Blog

Space for the articles related to business4s.org community

Forms4s: Rapid (Internal) UI Development

6 min readJan 22, 2026

--

Scala.js is genuinely great. It lets backend developers build web applications using the same language, type system, and mental models they already rely on every day. It’s mature, stable, and battle-tested.

And yet, almost nobody uses it for mainstream frontend development.

The reasons are mostly pragmatic. Finding Scala developers is already hard. Convincing a company to build its primary, customer-facing web product with Scala.js means betting on a niche within a niche. That’s a bet most businesses won’t take. There will always be enthusiasts — but they are the exception.

On top of that, many Scala developers (myself included) have limited interest in the traditional frontend ecosystem. The constant churn of frameworks, CSS systems, and build tools is not where most backend-leaning developers want to spend their time.

The conclusion many people draw from this is that Scala.js is a dead end.

It isn’t. We’ve just been aiming it at the wrong target.

The Right Niche: Internal and Back-Office Tools

Scala.js doesn’t need to compete with TypeScript and React for customer-facing products. There is a much better and more realistic opportunity: internal tools.

Admin panels, back-office dashboards, operational UIs — the kind of software every company needs, but nobody wants to overinvest in.

These applications tend to share a few characteristics:

  • Speed of development beats polish
    Every hour spent on internal tooling is an hour not spent on the core product.
  • Functionality over aesthetics
    As long as the UI is usable and doesn’t look terrible, nobody complains.
  • Backend developers often build them anyway
    Frontend teams are focused elsewhere — or don’t exist at all — and these tools require deep domain knowledge.

This is exactly where Scala.js shines. Backend developers can reuse their types, domain logic, and mental models. If a fancy UI widget is missing, it’s rarely a deal-breaker. Workarounds are acceptable, and product managers are usually fine with “good enough”.

So why don’t more teams already use Scala.js here?

The Missing Piece: Leverage

Even for internal tools, most teams default to TypeScript. Not because it’s perfect, but because the ecosystem gives them leverage.

In the TypeScript world, generating a form or a filterable table is often a one-library decision. In Scala.js, the same task traditionally meant wiring models, validation, rendering, and state management by hand — even for internal tools.

To make Scala.js compelling in this space, we need libraries that turn days of work into hours. They don’t have to be endlessly flexible or cover every edge case. They just need to solve the 80% case extremely well.

That’s where Forms4s comes in.

Forms4s: Case Class In, UI Out

Forms4s is a small, focused library for building user interfaces directly from Scala models, with first-class support for JSON-based APIs.

A minimal example looks like this:

case class User(name: String, email: String, age: Int) derives ToFormElem

val form = summon[ToFormElem[User]].get
val state = FormElementState.empty(form)

import forms4s.tyrian.BootstrapFormRenderer
val html = BootstrapFormRenderer.renderForm(state)

import forms4s.circe.*
val json = state.extractJson
val rebuilt = state.load(json)

From this, you immediately get:

  • A working HTML form
  • Validation
  • JSON extraction and loading
  • Reasonable default UI using Bootstrap, Bulma, or Pico.css

In practice, this means you can go from a backend case class to a working form — including validation and JSON wiring — in minutes, not hours.

The rendering layer is deliberately decoupled from the core model. Today, Tyrian is supported, but adding Laminar, Calico, or ScalaTags renderers is straightforward. The form model itself is framework-agnostic.

Forms can also be generated from JSON Schema, which makes integration with services written in other languages much easier — or enables fully dynamic forms where shapes of forms are returned from the API.

You can see it in action in the live demo.

Press enter or click to view image in full size

You can play with it yourself here!

Datatables: Because Every Internal Tool Has a Table

This originally a “maybe someday” idea. It quickly became obvious that they are not optional. Every internal tool has tables — and building them over and over again is pure friction.

Forms4s now includes a full datatable component, also derived from Scala types:

case class Order(id: Int, customer: String, total: BigDecimal, date: LocalDate)

val tableDef = TableDefBuilder[Order]
.modify(_.total)(_.withFilter(ColumnFilter.numberRange))
.modify(_.date)(_.withFilter(ColumnFilter.dateRange))
.modify(_.customer)(_.withFilter(ColumnFilter.text))
.build("orders")

val table = TableState(tableDef, myOrders)
val html = BulmaTableRenderer.render(table)

Out of the box, you get:

  • Filtering: text search, dropdowns, multi-select, number ranges, date ranges, booleans
  • Sorting and pagination
  • Row selection (single and multi-select)
  • CSV export, respecting current filters and selection
  • URL query parameter support, so filtered views can be shared
  • Server-side mode for large datasets

This covers the majority of internal use cases I’ve seen in real systems: order management, audit logs, user administration panels, and operational dashboards.

As with forms, the table definition is derived from your case class. You customize only what you need and rely on sensible defaults for the rest.

Press enter or click to view image in full size

Once again, play with it yourself here!

Styling Without Framework Lock-In

Forms4s deliberately leans toward pure-CSS frameworks. Those who tried Scala.js know that majority of its problem come from javascript ecosystems and interactions with it. We want to avoid that.

I’ve previously used Semantic UI (great defaults and abstraction level, but requires JavaScript and implicit runtime behavior) and experimented with Tailwind (powerful, but heavy to set up and operating at a very low level). For internal tools, both felt like unnecessary complexity.

Pure-CSS libraries like Bootstrap 5, Bulma, or Pico.css hit the sweet spot: good-looking UIs, predictable behavior, no JavaScript, and minimal setup.

This fits naturally with Forms4s’ rendering model. Rendering is defined by a small, explicit trait. Built-in renderers are intentionally “good enough”, and if you need custom styling, you replace a single renderer class.

That boundary is clear and cohesive — and particularly well-suited to the age of AI-assisted development. With a well-defined contract, generating a custom renderer for your own styling approach is something an AI can help you do quickly.

Styling becomes a replaceable concern, not a foundational decision you’re locked into.

Is This Still Relevant in the Age of AI?

It’s a fair question. With modern AI agents, it’s entirely plausible that a complete back-office UI can be generated in a matter of days — sometimes even faster.

But generation is only half the story. That code still has to live in production, be understood, adapted, and maintained over time. This is where I believe the real risk lies. It’s now easier than ever to end up with a lot of code that nobody truly understands — and eventually, even AI struggles to reason about.

Because of this, I think we should be focusing more than ever on minimizing the amount of code we put into production. Fewer moving parts, fewer custom abstractions, fewer hand-written layers.

Forms4s aligns naturally with this mindset. By deriving forms and tables from types, and by pushing variability into small, well-defined extension points, it reduces surface area rather than expanding it. You’re not asking AI to generate an entire UI framework for you — you’re asking it to help with small, local adaptations on top of a constrained, understandable core.

In an AI-assisted world, leverage doesn’t come from generating more code faster. It comes from having less code to maintain.

What’s Next

If Forms4s gains traction, there are several obvious directions to explore:

  • Laminar / Calico renderers
  • Play / Datastar integration
  • Slack and Discord form rendering

The broader goal is simple: make building internal tools with Scala.js fast enough that it becomes an obvious choice for backend Scala teams.

If you’re a backend Scala developer who occasionally has to build internal UIs, Forms4s is worth experimenting with today.

We are always happy to hear your feedback!

--

--

Voytek Pituła
Voytek Pituła

Written by Voytek Pituła

Generalist. An absolute expert in faking expertise. Claimant to the title of The Laziest Person in Existence. Staff Engineer @ SwissBorg.