<?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: Brio.js</title>
    <description>The latest articles on DEV Community by Brio.js (briojs).</description>
    <link>https://dev.to/briojs</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%2Forganization%2Fprofile_image%2F15648%2F7a902505-bee8-421c-9e3d-37febfd9b629.png</url>
      <title>DEV Community: Brio.js</title>
      <link>https://dev.to/briojs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/briojs"/>
    <language>en</language>
    <item>
      <title>My learning project became a JavaScript framework: Brio.js v5</title>
      <dc:creator>Jhonathan Reis</dc:creator>
      <pubDate>Wed, 28 Oct 2026 15:24:08 +0000</pubDate>
      <link>https://dev.to/briojs/my-learning-project-became-a-javascript-framework-briojs-v5-3l6e</link>
      <guid>https://dev.to/briojs/my-learning-project-became-a-javascript-framework-briojs-v5-3l6e</guid>
      <description>&lt;p&gt;On February 12, 2024, I committed a file with the message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A not so good scope architecture&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Five days later, the next commit said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;New scoping system&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the most honest summary of how Brio.js was built. I wrote something, I learned why it was wrong, and I wrote it again. I did that a lot, for fun and to learn how the frameworks I used every day actually work.&lt;/p&gt;

&lt;p&gt;At some point the "learning project" quietly started running in one small production project of mine. Then it got tests. Then docs. Then a playground, a starter CLI, and a website with a small creature on it (we'll get to the creature).&lt;/p&gt;

&lt;p&gt;This week I released &lt;strong&gt;Brio.js v5&lt;/strong&gt; and made the whole thing public. This post is the "hello, here's what I've been doing in my free time" post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Brio is
&lt;/h2&gt;

&lt;p&gt;Brio adds reactivity to HTML with &lt;code&gt;b-*&lt;/code&gt; attributes. It works on the real DOM, in place. No virtual DOM, no JSX, no required compiler, and no runtime dependencies.&lt;/p&gt;

&lt;p&gt;Here's the smallest useful Brio app. You can save it as a file and open it from any static server:&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;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"counter"&lt;/span&gt; &lt;span class="na"&gt;b-cloak&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;b-on-click=&lt;/span&gt;&lt;span class="s"&gt;"$data.count++"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Count: &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;b-text=&lt;/span&gt;&lt;span class="s"&gt;"$data.count"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/@jnreis/briojs@5.1.1/dist/brio.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;brio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#counter&lt;/span&gt;&lt;span class="dl"&gt;'&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;p&gt;(Add &lt;code&gt;&amp;lt;style&amp;gt;[b-cloak] { display: none; }&amp;lt;/style&amp;gt;&lt;/code&gt; so the button doesn't flash before Brio starts.)&lt;/p&gt;

&lt;p&gt;That's it: one script tag, one &lt;code&gt;mount&lt;/code&gt;, and a button that counts. If you want a build setup instead, &lt;code&gt;npm create brio@latest&lt;/code&gt; gives you a starter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the creature
&lt;/h2&gt;

&lt;p&gt;Every framework website needs a demo. Most use a todo list. I made a small creature with feelings.&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%2Fz5denamlbij4uihftdst.webp" 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%2Fz5denamlbij4uihftdst.webp" alt="A creature changes from happy blue Blip to sleepy purple Bean to mischievous pink Mochi as its controls change." width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You pick a mood, a color, a size, and a name, and the creature updates as you go. There's also a 🎲 &lt;strong&gt;Surprise me&lt;/strong&gt; button, because it's fun.&lt;/p&gt;

&lt;p&gt;Here's a trimmed version of the real markup from the homepage. The horns only exist while it's being mischievous:&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;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"img"&lt;/span&gt; &lt;span class="na"&gt;b-bind-aria-label=&lt;/span&gt;&lt;span class="s"&gt;"$data.greeting"&lt;/span&gt;
  &lt;span class="na"&gt;b-bind-class=&lt;/span&gt;&lt;span class="s"&gt;"['creature', 'is-' + $data.mood]"&lt;/span&gt;
  &lt;span class="na"&gt;b-bind-style=&lt;/span&gt;&lt;span class="s"&gt;"{ '--body': $data.color, '--size': $data.size / 100 }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"creature-zzz"&lt;/span&gt; &lt;span class="na"&gt;b-if=&lt;/span&gt;&lt;span class="s"&gt;"$data.mood === 'sleepy'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;z z z&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"creature-horns"&lt;/span&gt; &lt;span class="na"&gt;b-if=&lt;/span&gt;&lt;span class="s"&gt;"$data.mood === 'mischievous'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"creature-eyes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;i&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;i&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"mood"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"sleepy"&lt;/span&gt; &lt;span class="na"&gt;b-model=&lt;/span&gt;&lt;span class="s"&gt;"$data.mood"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 😴 Sleepy&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"range"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"70"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"130"&lt;/span&gt; &lt;span class="na"&gt;b-model.number=&lt;/span&gt;&lt;span class="s"&gt;"$data.size"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the greeting is just a getter on the state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This creature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mood&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sleepy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; needs a nap.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mood&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mischievous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is up to something.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is having a great day.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best part: the &lt;a href="https://briojs.com/play/" rel="noopener noreferrer"&gt;playground&lt;/a&gt; opens with this exact creature. You can edit its HTML, CSS, and JavaScript and see the changes right away. Give it a fourth mood. Give it a hat. I won't stop you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things I'm excited about
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Every value says where it came from
&lt;/h3&gt;

&lt;p&gt;In Brio templates you write &lt;code&gt;$data.count&lt;/code&gt;, &lt;code&gt;$props.label&lt;/code&gt;, or &lt;code&gt;$for.item&lt;/code&gt;. Not &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;label&lt;/code&gt;, or &lt;code&gt;item&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Yes, it's a little more typing. I went back and forth on this for a long time. It became a deliberate breaking change in 4.0, because once lists and components were nested inside each other, "where does &lt;code&gt;item&lt;/code&gt; come from?" stopped being a fun puzzle. Now you can read any expression and know which drawer to open. (There's a whole post coming on this decision.)&lt;/p&gt;

&lt;h3&gt;
  
  
  2. It only owns what you mount
&lt;/h3&gt;

&lt;p&gt;You can mount Brio around one form in a Rails view, one menu in a WordPress theme, or one calculator in an old PHP page. It doesn't care what's around it, and it won't touch anything outside the element you gave it. When the widget is removed, it cleans up after itself.&lt;/p&gt;

&lt;p&gt;That's the use case Brio was born from, and the one I care about most: adding interactivity to pages that already exist, without asking the rest of the page to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. It grows with you
&lt;/h3&gt;

&lt;p&gt;It starts with a single counter, but it doesn't stop there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Five starters&lt;/strong&gt; from &lt;code&gt;npm create brio&lt;/code&gt;: widget, multi-page, SPA, Astro islands, and plugin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional add-ons&lt;/strong&gt; for routing, HTTP, persistence, extra inputs, and observers. You load only what you use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A public plugin API&lt;/strong&gt; for your own directives and helpers. I moved some of Brio's own features out of the core into add-ons built on that same API, partly to prove it works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parts:&lt;/strong&gt; copy-paste dialog, disclosure, menu, and tabs components you own and can change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ten example apps&lt;/strong&gt;, from a counter to a TodoMVC, a 1,000-row table, a Kanban board, and a collaborative editor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration guides&lt;/strong&gt; for Rails, Laravel, Django, WordPress, and plain HTML with Express.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are 36 directives in total, and 1,258 tests that are much stricter with me than I am with myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it's not good at (yet)
&lt;/h2&gt;

&lt;p&gt;I'd rather you hear this from me than find out an hour into trying it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict CSP needs a separate build.&lt;/strong&gt; The standard build uses &lt;code&gt;new Function&lt;/code&gt;, which a policy that forbids &lt;code&gt;unsafe-eval&lt;/code&gt; blocks. Since 5.1 there's a CSP build (&lt;code&gt;@jnreis/briojs/csp&lt;/code&gt;) that runs without &lt;code&gt;eval&lt;/code&gt;. It hydrates only components defined in JavaScript, so plain page markup stays inert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side rendering.&lt;/strong&gt; Brio enhances HTML your server (or Astro) already rendered. It doesn't render on the server itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old browsers.&lt;/strong&gt; It targets modern evergreen browsers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's young, and it's one person.&lt;/strong&gt; I'm the only maintainer. The ecosystem is the docs, the examples, and whatever you and I build next. If you need a big community and long-term guarantees today, that's a fair reason to wait.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why share it now?
&lt;/h2&gt;

&lt;p&gt;Because I've reached the point where I can't see its flaws anymore. I know where everything is, so everything feels obvious. That's exactly when you need people who don't know where everything is.&lt;/p&gt;

&lt;p&gt;So here's my ask, and it's a small one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://briojs.com/play/" rel="noopener noreferrer"&gt;playground&lt;/a&gt;, break the creature, and tell me what confused you.&lt;/li&gt;
&lt;li&gt;Answer this in the comments: &lt;strong&gt;if you had to add interactivity to one part of a page you already have, what would it be?&lt;/strong&gt; A filter? A form that shows and hides fields? Something weirder?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every answer helps me decide what to write about and build next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://briojs.com/" rel="noopener noreferrer"&gt;briojs.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Playground: &lt;a href="https://briojs.com/play/" rel="noopener noreferrer"&gt;briojs.com/play&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/jnreis/briojs" rel="noopener noreferrer"&gt;github.com/jnreis/briojs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Discussions: &lt;a href="https://github.com/jnreis/briojs/discussions/5" rel="noopener noreferrer"&gt;Say hi or ask anything&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading. Bean says hi. Bean is also asleep.&lt;/p&gt;

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