Salvatore Pelligra
May 26 · 8 min read

User interfaces (UI from now on) are one of the most important factors of a user-facing app to ensure maximum engagement, yet building them is no easy task.

What makes building UI challenging is their dynamic, stateful and interactive nature; each user’s action changes some part of the app state and may trigger a number of other side effects, like requesting some new piece of information from a server, switching language, etc…

As is usual with programming, we have many ways to solve the same problem and solutions vary depending on the context.

CRUI (Composable Reactive UI) is a new library to help you build awesome UI for Web Applications by taking care of reactivity in a declarative way while ensuring that everything can be nicely composed together.

Disclaimer: CRUI is currently only available in Typescript and it’s still in alpha stage! Everything you will read from now on could drastically change.
The version used in this article is

ToDo Example

As is the custom, let’s make a ToDo app to showcase CRUI capabilities.

If you are unfamiliar with it, our acceptance criteria will be:

  • Add a new task to our todo list
  • Show a list of todos
  • Filter todos by visibility:
    * All
    * Tasks to complete
    * Already completed

The main components for our app will therefore be:

  • Title
  • Add new
  • Todo list
  • Filters

Let’s start from the easiest one: the Title.


Title is a static component, it will not change through any user interaction. Achieving this in CRUI is quite straightforward:

Congratulations, we just declared our first component!

Here we are declaring Title to be an h1 tag with a child text node TODO App .
We use hc to declare an element with children, while text is used to create a text node. Using text nodes is important to prevent XSS attacks and by default, this is the only way to insert texts with CRUI.

If you are familiar with React, you may be surprised about directly using an array for children, but for CRUI this is totally fine and indeed helps with composability.

Given that displaying elements with text is quite common, we also have a shortcut for it:

A component is just a definition of what needs to be done and to actually render it we need to mount it in the DOM:

The third parameter is the Context, you can ignore this detail given that will not cover it in this article.


Let’s now focus on interacting with the user. We want them to be able to enter a new todo into the list.

We will need an input tag, and let’s also add some style to it:

h is the general purpose Element and the base for most of the others. It receives a Setup which in this case will setup the className property for this particular element.

Given that we are using Typescript, props will ensure that only valid properties will be passed, so there is no way you can mistakenly use class.

To make this component useful, we also need to extract the value the user entered:

on will setup a specific event, the input event in this case. Given that we also need to setupprops , we need to combine the two using sc2 : setup combine 2, meaning that we can combine two setups into one.


The core package is all about composability and building UIs, hence, it doesn’t care at all about reactivity by design, it just set the foundation to support it.

A nice consequence of this choice is that reactivity in CRUI is just a library and therefore can be implemented in many different flavors.

The official R in CRUI comes from @crui/reactive package:

Here we declare once again our input component, but this time we bind its value with a StreamBox.

We decided to avoid the Virtual DOM and rather use Streams as reactivity building blocks. A Stream is conceptually an Observable: every time the value in $box changes, it will notify all of its observers.

bindValue will ensure that $box and input value are always synchronised, changing both sides accordingly.

This means that if we run:


The input value will immediately change too.


Now that we have a nice way to catch user input, we need a button to trigger the creation of a new todo:

sc combines any number of Setup into one.
ctext is a combination of child and text , hence it will add a new text node.
As the name suggest, onClick will setup a click event handler.

The declaration part should be familiar by now, the important bit is to understand how to actually perform this action.

Conceptually, we need to grab the value from the input and then push it into some sort of todo list. As in life, when you don’t know how to do something, just ask somebody else!

The attentive reader will notice that we are also clearing todo, ensuring a nice UX.

Add More Structure

Exposing just what we need is the best approach if you want to release a component as a library, however, given that we want to build an app here, we can go a little further and give it more structure:

Please keep in mind that the garbage collector will not be able to clean up Streams due to how the subscription mechanisms works, therefore it’s important to destroy them when not needed to avoid memory leaks.

Understanding the owner of a Stream (ie: who will destroy it) is fundamental to properly work with Stream, but keeping track of it can be tricky and prone to bugs. Guess who is good a keeping track? A compiler!

That’s why we offer set of interfaces that makes working with Stream quite pleasant. Let’s focus on getInput type signature:

getInput(): RW$B<string>

This funny return type stands for Read-Write $treamBox, which give us a Stream that we can freely manipulate, but cannot destroy! To be destroyable, it should also include the interface Destroyable .
By restricting the exposed type, we ensure that nobody else can destroy it and are also promising that we will take care of it.

The new submit Element will look like:

Update Input

The setup bindValue expect full ownership of the Stream provided, meaning that it will destroy it. However we just said that TodoStore is the actual owner of the input Stream, therefore we need to clone a Read-Write version of the provided stream, which is exactly what cloneRW does.


Now we have all we need to create the AddTodo:

Todo List

We need to display a list of all todos and we already have such a list, so we could be tempted to write something like:

This will work for the first render, but again there will be no knowledge of reactivity.

We need another Stream and @crui/reactive offers one that is optimised to work with lists: StreamList.

type TodoList = R$L<Todo>

Here we are restricting the exposed type even more to Read $treamList, hence we can only read value from it but not changing nor destroying it.

Only worth mentioning is that DRW$L stands for Destroyable Read-Write $treamList.

Next is the actual Todo list:

TodoList is now reactive and every new todo will be automatically added to the DOM as expected.

Toggle Todo state

Our Todo currently is just text, but we also need to mark it as completed:

You should by now appreciate how declarative this is. By just looking at the type, you can infer that done property is meant to change through time, while text will stay the same.

Let’s update our TodoStore:

And then our Todo element:

A checkbox will be displayed near the todo text. It’s worth mentioning that bindChecked will only work for input HTML tags and if you try to pass anything else, Typescript will complain! bindChecked will also set input type property to checkbox .

We further abstracted the concept of having an element with a CSS class and children in hcc , which is great to create small, focused utility functions that makes code more maintainable.


Next, we need to tackle the 3 filters. To build them we first have to start from the concept of visibility, ie: what we can filter for.

enum Visibility {

A single filter can just be a button that will set the appropriate visibility:

We will receive a Read-Write Stream of Visibility and a Visibility represented by this particular Filter.

Something worth mentioning is that map will create yet another StreamBox which will update accordingly with its parent stream; $props will then take care of clean it up once it’s not needed anymore.

Let’s now update the store to support visibility:

We only included what we need to add to the previous TodoStore definition, so to not confuse the reader.


This one is now straightforward:

Filtered TodoList

Even though we added the Visibility concept, our list has no knowledge of it.

Our list needs to react to 3 different user interactions:

  • A new item is pushed in the list
  • A new visibility filter is applied
  • A todo item state is toggled

We are covered for the first case and already have a stream for Visibility, so we can map that one into a stream of predicates:

Predicate<T> = (val: T) => boolean

Our first attempt could be:

This could do the trick, but toggling the todo item state would not activate the filter logic again, so what we really need is what we call a Stream of Predicates Streams:

$Predicate$<T> = R$B<Predicate$<T>>
Predicate$<T> = (val: T) => DR$B<boolean>

TodoStore will then be:

clone will just create a read-only, destroyable copy of that stream and it’s useful to avoid other parts of the code mess-up with the original stream.

The hardest part is done, now we can just declare our TodoList to be filtered by a $Predicate$. Luckily for us, there is a function that does exactly that:

c$filter$$ is similar to c$map, but other than mapping items it will also filter them accordingly. Given that it’s expecting the filter to generate a stream of booleans, it will also take care of destroying all of them once they are not needed anymore. Switching filters will destroy the current filter logic together with all the streams used by it, that’s why is important to use clone.

One thing I would like to point out is that both this function and c$map will not recalculate the whole list every time there is a change, but rather will surgical update the DOM based on stream updates. This means that the algorithmic complexity for adding/removing an element from the list is the same as Array.prototype.splice .

This was the last piece of the puzzle and with it, our implementation ends!
You can see the full example on GitHub: CRUI Todos

Thank you!

If you managed to read until here, thank you a lot and much appreciated!

I hoped this at least piqued your interest. I’m planning on releasing a couple more articles: one about how things work under the hood and another one explaining the motivations behind this library.

Stay tuned and see you next time!

Better Programming

Advice for programmers.

Salvatore Pelligra

Written by

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade