Writing GUI directly in HTML/CSS/JS is one of the very few ways to provide fully consistent UIs with responsiveness, among completely different devices, resolutions, DPIs and operating systems.
It works consistently on every minimally capable device in the world, today and in the future.
When done right, this is a good practice.
Having an independent layer for the UI is a good step in direction of separation of concerns, thus, it’s a good architecture practice (like front-end apps that communicates to back-ends through their APIs).
Also, it helps with mobile app encapsulation through Cordova or Capacitor. It can be extremely lightweight and fast as native when (1) reusing the mobile OS web engine (as webview on dekstop) and (2) when using a good processing model on JS side, like Svelte does with real reactivity.
This is not the only way.
We can use the same strategy of Tauri (see security), where they use the webview library but with no server nor open ports, using only the two-way language bindings for communication.
PS: The advantage of Axino (my experimental TS framework) is that an app written with Axino is 100% TS, so:
No complex bundler (no webpack config files). Jut Parcel.
It is compiled and packaged in one simple step.
There is compile-time check and type check etc. so it is basically safer than JS (and works better with VSCode).
But as I said in my previous post, it would be even simpler if it was 100% Crystal (if crystal could generate the JS or Wasm code needed on client-side).
This would make it possible to create apps in 100% Crystal (no HTML, CSS, JS, etc. needed for the users or the library).
In other words, it is possible to create 100% isomorphic apps (i.e. using Crystal on the « server-side » as well as on the « client-side »), hence resulting in a true app development kit 100% in Crystal.
To to put it another way : by using eval() and bind() the dom and its behavior (responding to events) can be written in Crystal, provided that the one creates a library which creates all the needed DOM elements from :
Crystal + eval() + bind()
Another way to look at it is : All that is needed to achieve an isomorphic app développement kit in Crystal / Webview is a an equivalent to Axino (or something similar) translated from TS to Crystal (which is a bit tedious to do, but not complicated).
I did the same thing for Golang (build a isomorphic GUI kit for Golang, which wraps JS code into Go functions):
Using GopherJS.
Using Go to Wasm compilation (transpiling).
And it works as expected.
(I have not published that code, because it is not ripe, but as an experiment it works)
I will try to test the concept with Crystal, as soon as I can install Webkit for Crystal on my Mac (does not work at the moment: I filed a bug report).
As of 2024, I’ve been working on a GUI library from the ground up.
It has fairly different design goals than other GUI frameworks that I’ve seen and the layout rules are quite different.
Features include
composable reactive components that can be templated together.
exposes a drawing API for freely rendering shapes / text / etc
decoupled from a backend (although currently only supports raylib out of the box)
automation capabilities baked in
some basic components provided
The library privileges the ability to easily create components as opposed to providing them. There is also support for font clamping/wrapping.
I’m in the process of learning a lot about a lot of technology I’ve taken for granted (a lot of ground to cover), so conversations are preferred over PRs and development will be a medium-slow burn.
If anyone wants to learn more, I put together a demo for a friend. (Thanks to nuclearbananana from reddit for compressing it). There are a few misteps and inaccuracies, but you’ll get the gist easily.
Why do you define UI and layout through templates (loosey strings, full of typos, which you need to parse at runtime?) and not through some similar nice DSL (pure code, checked by compiler)? What led you to this design? Thanks!
Hi, thanks for the criticism. I’m not sure what you mean by loosey strings full of typos, but the templates are parsed on mount. once. Decoupling the template from the code means that the code can focus on behavior and the presentation requires much less typing. Does this help?
I’m not sure what you mean by loosey strings full of typos,
I guess i can use some doc for lucky (it write template use purl Crystal) for explain what pfischer means.
Lucky uses Crystal methods for rendering HTML. The Crystal methods map as closely as possible to how HTML is used.
Using Lucky HTML adds an additional layer of type-safety, is auto-formatted with Crystal’s formatter, and can be much easier to refactor with regular Crystal methods as HTML gets larger.
Reading this thread, I can see the history of GUI development in Crystal.
At first, people asked for large and advanced toolkits. Later, expectations shifted toward smaller and simpler tools. Many GUI projects do not last long. Developers who use programming for work need the best tools to stay competitive, and they don’t have time for Crystal. So in the end, only the projects that can survive remain, even if they look a bit rough.
What I want is something small — just enough to build a remote control with a few buttons. For that reason, I rebuilt the old libui and made a library called UIng. There have been many libui bindings before, but I added some improvements:
Supports closures with Box, even for APIs that expect structs with function pointers.
Parent controls keep references to children, preventing GC from freeing memory by mistake.
Automated screenshots with GitHub Actions helped find and fix several bugs in libui itself.
Added experimental image drawing support (C code written interactively with AI).
Static libraries are built on GitHub Actions for Windows (MSVC, MinGW), macOS (x64, aarch64), and Linux (x64, aarch64). They can be downloaded from the GitHub Release via postinstall.
Because of libui’s limits, UIng is not the easiest library to use. But for the right cases, it is “good enough.” The real challenge is to keep maintaining it, and that is the hardest part.