<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexander van Elsas</title>
    <description>The latest articles on DEV Community by Alexander van Elsas (@avanelsas).</description>
    <link>https://dev.to/avanelsas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3862692%2F5e74f159-9ca2-4404-8049-b700a5002d21.jpeg</url>
      <title>DEV Community: Alexander van Elsas</title>
      <link>https://dev.to/avanelsas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avanelsas"/>
    <language>en</language>
    <item>
      <title>Fixing web component friction points</title>
      <dc:creator>Alexander van Elsas</dc:creator>
      <pubDate>Fri, 23 Oct 2026 08:36:11 +0000</pubDate>
      <link>https://dev.to/avanelsas/fixing-web-component-friction-points-2nnc</link>
      <guid>https://dev.to/avanelsas/fixing-web-component-friction-points-2nnc</guid>
      <description>&lt;p&gt;This post was originally posted on my &lt;a href="https://avanelsas.github.io/foggy-glasses/fixing-web-components-friction-points/" rel="noopener noreferrer"&gt;Foggy Glasses&lt;/a&gt; blog.&lt;/p&gt;

&lt;p&gt;In a previous post I discussed creating &lt;a href="https://avanelsas.github.io/foggy-glasses/web-components-in-cljs/" rel="noopener noreferrer"&gt;web components using ClojureScript&lt;/a&gt; and besides some of the advantages of web components I mentioned that there are some clear downsides as well. Web components aren't easy and for a component framework user it takes some mental gymnastics to wrap your head around the API.&lt;/p&gt;

&lt;p&gt;I wanted to learn about web components, ended up creating a web component library called BareDOM, and then expanded my knowledge of how to use web components by creating projects that use web components. I created BareBuild, a small ClojureScript runtime that supports server-state UIs, which comes with a very nice and powerful time-travel replay debugger called BareReplay. And of course, since everything is written in CLJS I ended up creating a bit of nREPL support to probe your web component implementation called BareProbe.&lt;/p&gt;

&lt;p&gt;I also tried to read up quite a bit on web components. Lots of opinions on them, from amazing to amazingly bad. A lot of the criticisms come from the problem that web components really are quite low level and that the browser platform doesn't really offer ways to deal with the challenges mentioned in this article. That is where frameworks shine, and that is also a problem as one of the promises web components give is that you are not tied to a specific framework. I started working on these challenges, and that is how BareMirror came about.&lt;/p&gt;

&lt;p&gt;I will try to explain some of these challenges and show how they can be addressed using &lt;a href="https://github.com/avanelsas/BareDOM" rel="noopener noreferrer"&gt;BareDOM&lt;/a&gt;. I have created a small set of functions inside that repository, called &lt;a href="https://github.com/avanelsas/baredom/blob/main/baremirror/README.md" rel="noopener noreferrer"&gt;BareMirror&lt;/a&gt;, that addresses some of these issues. Although BareMirror works well with BareDOM, there is no reason it cannot be used for other web component libraries. And although it is written in ClojureScript, it ships as a plain ES module for JavaScript and TypeScript.&lt;/p&gt;

&lt;p&gt;Here are some of the major challenges with web components.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. DOM rebuilding destroys UI state
&lt;/h3&gt;

&lt;p&gt;A common problem with an application rebuilding its HTML is that its existing DOM nodes are replaced. The result of this is that it destroys things like input focus, text selections, scroll positions, ongoing animations and even internal web component state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0j2a6o183qlj8csshi0p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0j2a6o183qlj8csshi0p.gif" alt="Losing Focus" width="759" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the animation above you can see how the focus is lost when the component is rebuilt on the left side, and what happens if you address that issue properly. What needs to be done is to reuse existing keyed nodes, preserve the focus, selection, scroll position and component state.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Updating the DOM manually is complicated
&lt;/h3&gt;

&lt;p&gt;While it is entirely possible to update the DOM yourself, it is a complex task. You need to find DOM elements, insert nodes, remove rows, and update attributes. As the complexity of your app increases, this task becomes more difficult to maintain. And if you get it wrong, the interface can display stale state. A targeted update has preference here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F306uv5jqiq49s9uhfrbb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F306uv5jqiq49s9uhfrbb.gif" alt="Full rebuild versus a targeted update" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the left side the entire calendar view is rebuilt, while on the right only the one value is updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Unnecessary DOM updates hurt performance
&lt;/h3&gt;

&lt;p&gt;The easy solution for re-rendering a page is to do the entire page. But this comes at a cost, especially when the page has complex components or large lists. Instead of re-rendering everything we can compare the last written values to the new values and then only update the attributes that have changed. Unchanged items receive no writes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqd1y5etb6dzv2llvsnz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqd1y5etb6dzv2llvsnz.gif" alt="Full rebuild versus reused nodes" width="720" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adding, deleting, reordering list items require deliberate DOM operations. Again, the simple solution is to rebuild it, but that loses state and node identity. A better solution is to add stable keys to each DOM node so that insert, remove and move operations to desired positions in the DOM become much easier to accomplish.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Component state and server state can disagree
&lt;/h3&gt;

&lt;p&gt;There are numerous ways in which component state and server state can disagree. An example is a web component asking the server for a state change that is rejected by the server. If there is no coordination, then the web component might show a value rejected by the server. The chosen solution is that the component asks, the app decides, and the component only shows what the state holds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3008dmvzn4lycvdlv1wf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3008dmvzn4lycvdlv1wf.gif" alt="No coordination versus coordination" width="759" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Event handling requires both DOM knowledge and boilerplate
&lt;/h3&gt;

&lt;p&gt;If you are implementing event handling, you need to write some boilerplate code to find what needs to be done and ensure it is taken care of. In BareMirror events are translated into declarative messages that contain both the action to be taken and the key of the row. The result is a standardised way to handle events.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjqfrhqhgojk7kd6mjsn1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjqfrhqhgojk7kd6mjsn1.gif" alt="Manual event handling versus meaningful messages" width="759" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Frameworks introduce architectural dependencies
&lt;/h3&gt;

&lt;p&gt;One of the main reasons to use web components is that it doesn't tie you to a framework. You use the browser platform and can therefore keep bundle size small and reuse the components in whatever framework you want to use. There is a simple reason that frameworks are popular, they solve problems that a web component does not even begin to address. However, if we look at these rendering challenges it turns out that most of them can be resolved with a relatively small set of functions. Updating is simpler when there are fewer things to update.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfjmq1wfbi3sc8ganvuk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfjmq1wfbi3sc8ganvuk.gif" alt="Framework update versus BareMirror updates" width="759" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What BareMirror does to resolve these issues
&lt;/h2&gt;

&lt;p&gt;BareMirror is a small set of functions that bring a page to what your application wants to show. The app keeps the state. The functions place the nodes, write the values and turn events into messages. It does not compare an old screen with a new one, just keys, and a node that stays is never made again. BareMirror only remembers what it wrote, on the nodes themselves. Each node holds a key, the item last written to it and the value last written to each of its properties.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3y7kx4gj3sgyxp9nd3is.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3y7kx4gj3sgyxp9nd3is.gif" alt="Form interaction to an updated DOM" width="760" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A cycle of 7 steps
&lt;/h3&gt;

&lt;p&gt;It works using a standard cycle of seven steps (event, message, step, state, view, render, page). It starts with an event (e.g. a user interaction), transforms it to a message that goes into a pure step function. The step function determines what the message does to affect state. From there we get the new state, put it into a view projection and then use a targeted write to update the DOM only there where it is needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on data instead of another DOM
&lt;/h3&gt;

&lt;p&gt;Instead of creating and keeping a second DOM tree somewhere in memory, BareMirror focuses on the data model. The basic idea is to keep track of a marked node called a part. A key marks the node in a list. Second, it defines templates, a fixed shape of a node that keeps track of where dynamic values can be written. These places are called holes, and that is where targeted writes can then be aimed at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh9dquuzlhtbnjgfra2z0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh9dquuzlhtbnjgfra2z0.gif" alt="Parts, templates, holes, keys and targeted writes" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the example above you can see a list of items where one of them is selected. BareMirror locates the node, looks at the template and fills the hole with a new value via a targeted write.&lt;/p&gt;

&lt;p&gt;The entire process is implemented in a set of functions that add about 7KB to a bundle. Instead of going through the code line by line, I will highlight a few elements to give you a feel for what it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Templates
&lt;/h3&gt;

&lt;p&gt;One core element is to create templates from data once, at the start. Instead of comparing old to new, we create templates with fixed and dynamic elements (holes).&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:li&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:x-checkbox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:checked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:done?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"x-checkbox-change-request"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:toggle&lt;/span&gt;&lt;span class="p"&gt;}}]&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:span&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:x-button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"press"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:remove&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Remove"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a row with html elements and web components. We use &lt;code&gt;template/split&lt;/code&gt; to generate the template for this. It will create the following data structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:fixed&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:li&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:x-checkbox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:data-x-part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"row.0"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:span&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:data-x-part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"row.1"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:x-button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:data-x-part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"row.2"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Remove"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:holes&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"row.0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:attrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:checked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:done?&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"row.1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="s"&gt;"x-checkbox-change-request"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"row.0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:toggle&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"press"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"row.2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:remove&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, fixed items do not need a name, but things that can change do get a name. A hole is just a function of the item. In Clojure a keyword is a function so &lt;code&gt;:done?&lt;/code&gt; is just a function that can be used on item &lt;code&gt;(:done? item)&lt;/code&gt;. An event uses the part name of its node to allow single map lookups.&lt;/p&gt;

&lt;p&gt;From this we can write helper functions to figure out where we need to write new values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hole&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hole&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hole-writes&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"The part name of `hole` and its writes for `item`."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cond-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:attrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;update-vals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:props&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;update-vals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))))])&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"The writes of `item` for the holes of `split-template`, by part name."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;split-template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hole-writes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:holes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;split-template&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the item &lt;code&gt;{:done? true :text "Buy milk"}&lt;/code&gt;, the writes function gives &lt;code&gt;{"row.0" {:attrs {:checked true}}, "row.1" {:text "Buy milk"}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And then there is the &lt;code&gt;parts/write-parts!&lt;/code&gt; function that does the actual writing. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write-part!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Applies the write of one part to its node: the text, then the attributes, then the
   properties."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;part-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;}]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part-name&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;contains?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set-text!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set-attrs!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;set-prop!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write-parts!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Applies `writes` to `parts`, which are maps by part name: the nodes as `read-parts` gives
   them, and `:text`, `:attrs` and `:props` as `template/writes` gives them. A property is
   written when its value differs from the value last written to it, as `same-value?` sees it.
   Throws before any write when a part name has no node."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;when-some&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;missing-parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ex-info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"write-parts!: no node has these part names."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;vec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;)})))&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write-part!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that writes only happen if a value, an attribute or property has changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plans
&lt;/h3&gt;

&lt;p&gt;The next idea is to use a standard set of steps to determine what needs to update with a change. That is done with plans.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plan/plan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"b"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"c"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"d"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"d"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"b"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"c"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"x"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt; {:remove []&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="c1"&gt;;;     :place  [{:key "x" :in :todo :before "e"}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="c1"&gt;;;              {:key "d" :in :todo :before "b"}]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above you see how the plan function takes an old and a new situation and determines how to get from old to new. There are only two basic operations, remove, and place. Insert and move are both a place. A key with no node gets a new one, and a key that already has a node keeps it.&lt;/p&gt;

&lt;p&gt;Plan says where a node needs to go if it requires some form of update. The code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;placements&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"The placements that turn `current` into `wanted` in one container, in the order to perform them.
   The keys that both share at the start and at the end are left alone."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ends&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shared-ends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;stays&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;longest-rising&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ranks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;middle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ends&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;middle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ends&lt;/span&gt;&lt;span class="p"&gt;)))]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;comp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stays-at?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stays&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;placement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;container-placements&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"The placements for `container`, which either side may leave out."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;placements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;vec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;vec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"What must happen to turn the `current` places into the `wanted` places.
   Both map a container to its keys, in order. A key is in at most one container."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:place&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wanted-anywhere&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;among&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;comp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted-anywhere&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:place&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mapcat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;partial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;container-placements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;distinct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;))))})))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, note that the code tries to avoid updating anything that doesn't change. The &lt;code&gt;placements&lt;/code&gt; function needs some explanation. BareMirror keeps children in line with a list of keys. It tries to find the fewest DOM moves to get from a current to a wanted situation. Example: A list of child keys might look like &lt;code&gt;[a b c d e]&lt;/code&gt; and the new order wanted is &lt;code&gt;[a d b c x e]&lt;/code&gt;. Placements performs 4 steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trim the shared ends. Changes often happen somewhere in the middle. In this case keys that already match at the start (a) and at the end (e) are left alone. That leaves &lt;code&gt;[b c d]&lt;/code&gt; now and &lt;code&gt;[d b c x]&lt;/code&gt; wanted.&lt;/li&gt;
&lt;li&gt;Rank the middle. Each wanted key gets its position in the current middle, e.g. &lt;code&gt;[d b c x]&lt;/code&gt; becomes &lt;code&gt;[2 0 1 nil]&lt;/code&gt;, where nil means the key isn't in the list yet.&lt;/li&gt;
&lt;li&gt;Keep the longest increasing subsequence. The longest series of ranks going up, not necessarily side by side is &lt;code&gt;0 1&lt;/code&gt;, which is &lt;code&gt;[b c]&lt;/code&gt;. These keys are already correctly placed, so they stay put, which leads to fewer moves needed.&lt;/li&gt;
&lt;li&gt;Place the rest back to front. Each remaining key is put before its right-hand neighbour in the wanted list. &lt;code&gt;x&lt;/code&gt; goes before &lt;code&gt;e&lt;/code&gt;. &lt;code&gt;d&lt;/code&gt; goes before &lt;code&gt;b&lt;/code&gt;. etc. Going backwards guarantees that neighbour is already at the right place.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keys no longer needed are not placed but removed. All of this is planning. It doesn't really change anything, it only computes what needs to change. It is a pure function so there are no side effects to worry about.&lt;/p&gt;

&lt;p&gt;Placing nodes takes three steps. &lt;code&gt;read-places&lt;/code&gt; reads the current keys from the DOM, &lt;code&gt;plan&lt;/code&gt; compares them with the wanted keys, and &lt;code&gt;perform!&lt;/code&gt; carries out the plan. It inserts and moves only the nodes the plan names and removes the dropped ones, and every node that stays is left untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demos
&lt;/h2&gt;

&lt;p&gt;The code comes with several demos, but the mirror demo shows a lot of what BareMirror provides. It splits the screen into 2, on the left side a JS implementation of a task list, and on the right the same task list but now implemented with BareMirror.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftnndp9vl4puzk1hb2er8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftnndp9vl4puzk1hb2er8.gif" alt="Full update versus targeted update" width="600" height="745"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that any manipulation of a task leads to a full update (flashing colors) on the left, and a dedicated update on the right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F46fuscondd3mbb5vsda3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F46fuscondd3mbb5vsda3.gif" alt="Losing focus" width="600" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another interesting thing is to note that when another user is manipulating the same task list you lose focus in the note input field. This can be prevented by not recreating the node that has focus, but keeping it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdub6q27mlm1n4qrgw5xb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdub6q27mlm1n4qrgw5xb.gif" alt="Re-rendering lists" width="600" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we increase the number of tasks to 1000 then the constant re-rendering becomes an issue. Just look at the time it takes to update the left versus the right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5oc55n5tbe6fzc13nkh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5oc55n5tbe6fzc13nkh.gif" alt="Hand-built still wins" width="600" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Lit?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://lit.dev/" rel="noopener noreferrer"&gt;Lit&lt;/a&gt; is a mature solution and offers most of the things you could want when dealing with web components. Lit uses a slightly different approach than BareMirror to solve these rendering issues. Lit remembers what it rendered last in memory. It compares against its memory and then updates the DOM. It never reads the DOM back, which makes it faster.&lt;/p&gt;

&lt;p&gt;BareMirror keeps no such memory or state outside the DOM. It reads the DOM on every render, which makes the DOM the only source of truth. Although this is slower, the most important advantage of this approach is that whatever must change is just a value that you can inspect before it reaches the DOM. It is also likely to be a bit faster when you are moving heavy rows from one list to another. Lit will destroy the node and create a new one, BareMirror won't.&lt;/p&gt;

&lt;p&gt;We can compare performance of Lit versus BareMirror with the 1000 row demo, by performing tasks on them and then measuring how long it takes. First a comparison using BareDOM web components:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;BareMirror (ms)&lt;/th&gt;
&lt;th&gt;Lit 3.3.0 (ms)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;create 1000&lt;/td&gt;
&lt;td&gt;109.2&lt;/td&gt;
&lt;td&gt;91.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rotate one&lt;/td&gt;
&lt;td&gt;1.7&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;toggle one&lt;/td&gt;
&lt;td&gt;1.2&lt;/td&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;toggle every 10th&lt;/td&gt;
&lt;td&gt;13.0&lt;/td&gt;
&lt;td&gt;10.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rename every 10th&lt;/td&gt;
&lt;td&gt;1.9&lt;/td&gt;
&lt;td&gt;1.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;swap rows 2 and 999&lt;/td&gt;
&lt;td&gt;1.4&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;remove one&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;replace all 1000&lt;/td&gt;
&lt;td&gt;121.1&lt;/td&gt;
&lt;td&gt;100.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clear&lt;/td&gt;
&lt;td&gt;9.9&lt;/td&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To exclude any overhead of BareDOM we can do the same benchmark with plain html elements:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;BareMirror (ms)&lt;/th&gt;
&lt;th&gt;Lit 3.3.0 (ms)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;create 1000&lt;/td&gt;
&lt;td&gt;20.6&lt;/td&gt;
&lt;td&gt;17.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rotate one&lt;/td&gt;
&lt;td&gt;1.3&lt;/td&gt;
&lt;td&gt;0.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;toggle one&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;toggle every 10th&lt;/td&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;td&gt;2.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rename every 10th&lt;/td&gt;
&lt;td&gt;1.3&lt;/td&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;swap rows 2 and 999&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;remove one&lt;/td&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;replace all 1000&lt;/td&gt;
&lt;td&gt;23.6&lt;/td&gt;
&lt;td&gt;20.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clear&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;2.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not bad for a first attempt, but Lit is clearly the more mature and faster solution for these challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;p&gt;The BareMirror code is still alpha and therefore in the BareDOM repo for now. It works as is, and it is used in several other projects, including in an open-source blog generator called &lt;a href="https://github.com/avanelsas/bareblog" rel="noopener noreferrer"&gt;BareBlog&lt;/a&gt; (which powers the Foggy Glasses blog).&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>baredom</category>
    </item>
    <item>
      <title>BareForge v0.1.1: A drag-and-drop UI design tool that bridges the gap between design and code</title>
      <dc:creator>Alexander van Elsas</dc:creator>
      <pubDate>Tue, 28 Apr 2026 14:02:13 +0000</pubDate>
      <link>https://dev.to/avanelsas/bareforge-v011-a-drag-and-drop-ui-design-tool-that-bridges-the-gap-between-design-and-code-4nfl</link>
      <guid>https://dev.to/avanelsas/bareforge-v011-a-drag-and-drop-ui-design-tool-that-bridges-the-gap-between-design-and-code-4nfl</guid>
      <description>&lt;p&gt;I've just released &lt;strong&gt;&lt;a href="https://github.com/avanelsas/bareforge" rel="noopener noreferrer"&gt;BareForge v0.1.1&lt;/a&gt;&lt;/strong&gt;. BareForge aims to close the gap between design and code. What does it offer:&lt;/p&gt;

&lt;p&gt;• Drag-and-drop any of the 90 BareDOM components onto a canvas&lt;br&gt;
• Tweak attributes, layout, CSS-variable themes live&lt;br&gt;
• Declare reactive state on any container — fields, computed fields, records&lt;br&gt;
• Wire DOM events to actions that mutate the data&lt;br&gt;
• Pick from 9 starter templates (8 realistic + 1 kinetic showcase) or start blank&lt;/p&gt;

&lt;p&gt;It exports a fully working vanilla JS project (or Clojurescript if that is your cup of tea). The code output looks pretty decent so far.&lt;/p&gt;

&lt;p&gt;The BareForge export is pluggable. Adding React, Svelte or any other framework can be implemented in a simple manner.&lt;/p&gt;

&lt;p&gt;It would be great if someone would give that a try.&lt;/p&gt;

&lt;p&gt;The code can be found at github: &lt;a href="https://github.com/avanelsas/bareforge" rel="noopener noreferrer"&gt;Bareforge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and a live demo is at: &lt;a href="https://avanelsas.github.io/bareforge/" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>ui</category>
      <category>webdev</category>
      <category>design</category>
    </item>
    <item>
      <title>BareDom v2.0.0: 80 Components, customisable themes, responsive, and Zero Framework Tax</title>
      <dc:creator>Alexander van Elsas</dc:creator>
      <pubDate>Fri, 10 Apr 2026 20:13:04 +0000</pubDate>
      <link>https://dev.to/avanelsas/baredom-v200-80-components-customisable-themes-responsive-and-zero-framework-tax-1c3f</link>
      <guid>https://dev.to/avanelsas/baredom-v200-80-components-customisable-themes-responsive-and-zero-framework-tax-1c3f</guid>
      <description>&lt;p&gt;I’ve just released BareDom v2.0.0, and it’s the biggest update yet. BareDom has always been about "No framework, just the DOM," but v2.0.0 brings the library into a new form: a fully responsive design system powered by a centralized theming engine.&lt;/p&gt;

&lt;p&gt;If you’ve been looking for a way to build high-performance, beautiful UIs without the weight of a heavy framework, this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The New Theming Engine: x-theme&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The star of this release is x-theme. Instead of hunting through CSS variables for every component, you can now control your entire UI through one centralized component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;br&gt;
33 Design Tokens: Precise control over typography, spacing, shadows, and colors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8 Built-in Presets:&lt;/strong&gt; Instantly switch between ocean, forest, sunset, neo-brutalist, aurora, mono-ai, warm-mineral, and the default theme.&lt;/p&gt;

&lt;p&gt;registerPreset() API: Create your own custom themes with partial merging, making it incredibly easy to brand your app.&lt;/p&gt;

&lt;p&gt;The new theme picker is used on all 80 demo pages!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile-First &amp;amp; Fully Responsive&lt;/strong&gt;&lt;br&gt;
In v2.0.0, all 80 components are overhauled for responsiveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile-Ready:&lt;/strong&gt; Components now adapt seamlessly to smaller screens.&lt;br&gt;
The demo pages now work on both desktop and mobile. Interaction states and spacing have been refined for better mobile usability.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Why BareDom?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;BareDom is built on a unique stack: ClojureScript compiled to tiny, tree-shakeable ES modules via Google Closure. It gives you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero Runtime Dependency:&lt;/strong&gt; Your users don't download a framework to render a button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standards First:&lt;/strong&gt; Built on Custom Elements v1 and Shadow DOM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lightweight:&lt;/strong&gt; Each component is its own module. Use only what you need.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;Setting up a themed UI is now easier than ever:&lt;/p&gt;

&lt;p&gt;HTML&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;x-theme&lt;/span&gt; &lt;span class="na"&gt;preset=&lt;/span&gt;&lt;span class="s"&gt;"neo-brutalist"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;x-button&lt;/span&gt; &lt;span class="na"&gt;variant=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello BareDom v2&lt;span class="nt"&gt;&amp;lt;/x-button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/x-theme&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;initTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist/x-theme.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;initButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist/x-button.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;initTheme&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;initButton&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check out the 80 Demos!
&lt;/h2&gt;

&lt;p&gt;I’ve updated every single demo page with the new theme picker so you can see x-theme in action across the entire library.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/avanelsas/baredom" rel="noopener noreferrer"&gt;avanelsas/baredom&lt;/a&gt;&lt;br&gt;
Live demos: &lt;a href="https://avanelsas.github.io/baredom/" rel="noopener noreferrer"&gt;BareDOM Github Page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’d love to hear which of the 8 new presets is your favorite!.&lt;/p&gt;

&lt;h1&gt;
  
  
  webdev #showdev #webcomponents #clojurescript #javascript #uidesign
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>uidesign</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Native Web Components don't have to be boring. Meet BareDOM v1.3.0</title>
      <dc:creator>Alexander van Elsas</dc:creator>
      <pubDate>Tue, 07 Apr 2026 18:25:57 +0000</pubDate>
      <link>https://dev.to/avanelsas/native-web-components-dont-have-to-be-boring-meet-baredom-v130-4m6e</link>
      <guid>https://dev.to/avanelsas/native-web-components-dont-have-to-be-boring-meet-baredom-v130-4m6e</guid>
      <description>&lt;p&gt;Announcing v1.3.0 of &lt;a href="https://github.com/avanelsas/baredom" rel="noopener noreferrer"&gt;BareDOM&lt;/a&gt;&lt;br&gt;
I wanted to add some modern, fun, kinetic, organic, and animated UI elements that feel alive, without the 50kb runtime tax. The latest update to BareDOM moves away from static UI elements and introduces components designed with motion and fluidity in mind. &lt;/p&gt;

&lt;p&gt;New web components include :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Effects&lt;/strong&gt; : x-ripple-effect, x-gaussian-blur, x-splash, x-metaball-cursor, x-neural-glow, x-organic-progress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout&lt;/strong&gt; : x-bento-grid, x-bento-item&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scroll&lt;/strong&gt; : x-scroll-stack, x-scroll-parallax, x-scroll-timeline, x-scroll-story&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typography&lt;/strong&gt; : x-typography, x-kinetic-typography&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decorative&lt;/strong&gt; : x-organic-divider, x-organic-shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why go Bare?&lt;/p&gt;

&lt;p&gt;BareDOM is built in ClojureScript but delivered as pure ESM or via NPM. You get the benefit of sophisticated functional logic under the hood, but for your project, it’s just a standard tag.&lt;/p&gt;

&lt;p&gt;Why BareDOM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Works in any stack.&lt;/strong&gt; Because components are native HTML elements, they work wherever HTML works — vanilla JavaScript, React, Vue, Svelte, Angular, server-rendered HTML, or a static page. No adapter layer, no wrapper library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No framework lock-in.&lt;/strong&gt; Your components are not tied to the framework you are building with today. Migrate your app, keep your components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tree-shakeable by design.&lt;/strong&gt; Each component is a separate ES module. Import only what you use; bundle tools eliminate the rest automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full theming with CSS custom properties.&lt;/strong&gt; Every visual detail — colours, spacing, radius, shadows, typography — is exposed as a &lt;code&gt;--x-&amp;lt;component&amp;gt;-&amp;lt;property&amp;gt;&lt;/code&gt; CSS custom property. Override at any scope: globally, per-page, per-instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Light and dark mode included.&lt;/strong&gt; All components adapt automatically to &lt;code&gt;prefers-color-scheme&lt;/code&gt;. No JavaScript required, no class toggling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility built in.&lt;/strong&gt; ARIA roles, live regions, keyboard navigation, focus management, and &lt;code&gt;prefers-reduced-motion&lt;/code&gt; support are part of the component, not an afterthought. You do not need to layer accessibility on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Shadow DOM.&lt;/strong&gt; Shadow roots are &lt;code&gt;mode: "open"&lt;/code&gt; — inspectable in DevTools, styleable via &lt;code&gt;::part()&lt;/code&gt;, and testable with standard DOM APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See it in &lt;a href="https://avanelsas.github.io/baredom/" rel="noopener noreferrer"&gt;action&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out the repo, grab a few components, and let me know what you think!&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>animation</category>
      <category>kinetic</category>
      <category>showdev</category>
    </item>
    <item>
      <title>BareDOM: 67 Zero-Dependency Web Components (Built with CLJS)</title>
      <dc:creator>Alexander van Elsas</dc:creator>
      <pubDate>Sun, 05 Apr 2026 19:48:29 +0000</pubDate>
      <link>https://dev.to/avanelsas/baredom-67-zero-dependency-web-components-built-with-cljs-2n1i</link>
      <guid>https://dev.to/avanelsas/baredom-67-zero-dependency-web-components-built-with-cljs-2n1i</guid>
      <description>&lt;p&gt;Like most ClojureScript developers, I started building UIs with Reagent and Re-frame. They're great tools. But as my UIs grew larger and more complex, I kept rebuilding the same generic components from scratch. Bundle sizes crept up. Framework lock-in felt heavier with each project.&lt;/p&gt;

&lt;p&gt;I started looking for a different approach and discovered Web Components. Native. Framework-free. Works anywhere HTML works. I built a few by hand, but never had the spare time to develop a full set.&lt;/p&gt;

&lt;p&gt;While experimenting with Claude Code, I realised that 1 + 1 could be 3. That's how &lt;strong&gt;BareDOM&lt;/strong&gt; was born — my first open-source project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is BareDOM?
&lt;/h2&gt;

&lt;p&gt;BareDOM is a library of &lt;strong&gt;67 UI components&lt;/strong&gt; built entirely on web standards — Custom Elements v1, Shadow DOM, and ES modules. No framework runtime. No virtual DOM. No peer dependencies.&lt;/p&gt;

&lt;p&gt;The rendering model is deliberately simple:&lt;/p&gt;

&lt;p&gt;DOM = f(attributes, properties)&lt;/p&gt;

&lt;p&gt;Components are &lt;strong&gt;stateless&lt;/strong&gt;. Set an attribute, the DOM updates. Remove it, it updates back. No hidden reactive systems, no signals, no state management to learn.&lt;/p&gt;

&lt;p&gt;It's authored in ClojureScript and compiled to optimised ES modules using Google Closure's advanced compilation. But you don't need to know ClojureScript to use it — it's just HTML elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it live
&lt;/h2&gt;

&lt;p&gt;Browse all 67 components with interactive demos:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://avanelsas.github.io/baredom/" rel="noopener noreferrer"&gt;https://avanelsas.github.io/baredom/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Form:&lt;/strong&gt; button, checkbox, switch, slider, select, date-picker, search-field, text-area, and more&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feedback:&lt;/strong&gt; alert, badge, toast, notification-center, progress, spinner, skeleton&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigation:&lt;/strong&gt; navbar, sidebar, tabs, breadcrumbs, pagination, menu&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layout:&lt;/strong&gt; card, grid, container, collapse, divider, spacer&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data:&lt;/strong&gt; table, chart, timeline, avatar, stat&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overlay:&lt;/strong&gt; modal, drawer, dropdown, popover, command-palette, context-menu&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual/Animation:&lt;/strong&gt; scroll-parallax, scroll-story, kinetic-typography, ripple-effect, organic-shape, gaussian-blur&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;p&gt;No build tool? No problem. Drop this into any HTML page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist/x-button.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;x-button&lt;/span&gt; &lt;span class="na"&gt;variant=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt; &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"md"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/x-button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. A script tag and an HTML element. The browser loads only the modules you import.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prefer npm? That works too:
&lt;/h2&gt;

&lt;p&gt;npm install @vanelsas/baredom&lt;/p&gt;

&lt;p&gt;import { init } from '@vanelsas/baredom/x-button';&lt;br&gt;
init();&lt;/p&gt;

&lt;p&gt;No framework setup, no providers, no config. Works in vanilla JS, React, Vue, Svelte, Angular, or a static page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Zero runtime — no framework overhead, just native custom elements&lt;/li&gt;
&lt;li&gt;Tree-shakeable — each component is a separate ES module, import only what you need&lt;/li&gt;
&lt;li&gt;Full theming — every visual property exposed as CSS custom properties (--x-button-bg, etc.)&lt;/li&gt;
&lt;li&gt;Dark mode built-in — all components adapt to prefers-color-scheme automatically&lt;/li&gt;
&lt;li&gt;Accessibility included — ARIA roles, keyboard navigation, focus management, reduced motion support&lt;/li&gt;
&lt;li&gt;Open Shadow DOM — inspectable, styleable via ::part(), testable with standard APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/avanelsas/baredom" rel="noopener noreferrer"&gt;https://github.com/avanelsas/baredom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@vanelsas/baredom" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@vanelsas/baredom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/avanelsas/baredom/blob/main/docs/javascript-guide.md" rel="noopener noreferrer"&gt;https://github.com/avanelsas/baredom/blob/main/docs/javascript-guide.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://avanelsas.github.io/baredom/" rel="noopener noreferrer"&gt;https://avanelsas.github.io/baredom/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;I'd love feedback from both JavaScript and ClojureScript developers. Try a component, file an issue, or just tell me what you think. The current version is v1.2.0 — more components and refinements are coming.&lt;/p&gt;

&lt;p&gt;BareDOM is MIT licensed and open source.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
