<?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: Amith Moorkoth</title>
    <description>The latest articles on DEV Community by Amith Moorkoth (@amithmoorkoth).</description>
    <link>https://dev.to/amithmoorkoth</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F936156%2F993f567b-554a-4995-af41-e91910f3d9eb.png</url>
      <title>DEV Community: Amith Moorkoth</title>
      <link>https://dev.to/amithmoorkoth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amithmoorkoth"/>
    <language>en</language>
    <item>
      <title>Lessons from Building Bombie: SPA Deep Links, CSP, and an Iframe Preview</title>
      <dc:creator>Amith Moorkoth</dc:creator>
      <pubDate>Thu, 21 May 2026 16:56:50 +0000</pubDate>
      <link>https://dev.to/amithmoorkoth/lessons-from-building-bombie-spa-deep-links-csp-and-an-iframe-preview-1ke3</link>
      <guid>https://dev.to/amithmoorkoth/lessons-from-building-bombie-spa-deep-links-csp-and-an-iframe-preview-1ke3</guid>
      <description>&lt;p&gt;This is the final post in the &lt;strong&gt;&lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;Bombie&lt;/a&gt;&lt;/strong&gt; series. The earlier ones covered what Bombie is, the JSON-tree architecture, and how to add a new component. This one is about the operational stuff that's invisible when it works — and obvious when it doesn't.&lt;/p&gt;

&lt;p&gt;Three lessons, each with the same shape: a problem I didn't anticipate, the fix that landed in the repo, and what I'd do the same way next time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: SPA deep links on GitHub Pages need a shim
&lt;/h2&gt;

&lt;p&gt;The first time I deployed Bombie to GitHub Pages, the builder route worked from the homepage. Click "Open builder", land at &lt;code&gt;/bombie/generate-component&lt;/code&gt;, everything renders. Then I refreshed the page and got a 404.&lt;/p&gt;

&lt;p&gt;GitHub Pages serves static files. It doesn't know anything about React Router. When you hit &lt;code&gt;/bombie/generate-component&lt;/code&gt; directly, Pages looks for a file at that path, doesn't find one, and returns its 404 page. Vercel handles this with a single &lt;code&gt;rewrites&lt;/code&gt; rule in &lt;code&gt;vercel.json&lt;/code&gt; — anything that doesn't match a real file rewrites to &lt;code&gt;/index.html&lt;/code&gt;, React Router takes over from there. GitHub Pages has no equivalent.&lt;/p&gt;

&lt;p&gt;The workaround Bombie uses lives in two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;public/404.html&lt;/code&gt; — the 404 page Pages returns when it can't find a file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public/spa-redirect.js&lt;/code&gt; — a tiny inline script in that 404 page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script reads the unfindable path, encodes it as a query string, and redirects to the root &lt;code&gt;index.html&lt;/code&gt; with that query string attached. React Router boots, sees the query string, and pushes the real route into history. The 404 was real but it's now invisible — one extra redirect, no observable difference to the user past the initial flash.&lt;/p&gt;

&lt;p&gt;The trick isn't novel (it's the &lt;a href="https://github.com/rafgraph/spa-github-pages" rel="noopener noreferrer"&gt;Rafgraph spa-github-pages&lt;/a&gt; pattern, well-documented in the React Router community) but the part worth saying out loud is that you'll never notice the problem until you ship to a static host that doesn't do rewrites. Local dev with &lt;code&gt;webpack-dev-server&lt;/code&gt; does rewrite to &lt;code&gt;index.html&lt;/code&gt; by default. Vercel does too. GitHub Pages doesn't. If your deploy story might ever cross hosts, get this in early.&lt;/p&gt;

&lt;p&gt;What I'd do the same next time: write the shim, but also pick one canonical host. Bombie ended up on &lt;strong&gt;Vercel&lt;/strong&gt; (&lt;code&gt;bombie-three.vercel.app&lt;/code&gt;) because the rewrite is a one-liner and there's no flash. The GitHub Pages workflow stayed in the repo for anyone who wants to deploy a fork there, with the shim handling the corner case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: One CSP for dev and prod is wrong
&lt;/h2&gt;

&lt;p&gt;Content Security Policy is one of those things that looks easy on the homepage and gets weird in practice. The default advice ("set &lt;code&gt;script-src 'self'&lt;/code&gt;") is correct for production. It's also incompatible with &lt;code&gt;webpack-dev-server&lt;/code&gt;'s HMR client, which uses &lt;code&gt;eval&lt;/code&gt; for hot module replacement.&lt;/p&gt;

&lt;p&gt;So you have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make production weaker so dev works.&lt;/li&gt;
&lt;li&gt;Make dev and prod use different CSPs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bombie picks option 2. The CSP is set per-mode in the webpack config:&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="c1"&gt;// roughly&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isDev&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script-src 'self' 'unsafe-eval'; ...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script-src 'self'; ...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;'unsafe-eval'&lt;/code&gt; is only in the dev CSP. The production bundle gets the strict policy. There's no &lt;code&gt;'unsafe-eval'&lt;/code&gt; in the running app once you &lt;code&gt;npm run build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reason I'm flagging this: it's tempting to drop &lt;code&gt;'unsafe-eval'&lt;/code&gt; into the meta tag and forget about it, because the dev console will stop complaining and prod won't &lt;em&gt;immediately&lt;/em&gt; break. But CSP violations don't fail loudly in production — they just block scripts. You won't notice until someone tries to use a third-party widget you added later that happens to call &lt;code&gt;eval&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Set the strict policy in prod from day one, even if you're not currently using anything that would benefit. Then if a future dependency starts calling &lt;code&gt;eval&lt;/code&gt; and the CSP blocks it, you'll see the violation immediately and decide whether to allow it explicitly or replace the dependency. With a loose CSP you'd just ship a quiet regression.&lt;/p&gt;

&lt;p&gt;The dev-mode &lt;code&gt;'unsafe-eval'&lt;/code&gt; is fine because it never ships. The build pipeline strips the dev block of the config and only the prod CSP lands in the deployed &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: Use an iframe when you need an honest viewport
&lt;/h2&gt;

&lt;p&gt;This one I covered briefly in the &lt;a href="https://dev.to/amithmoorkoth/how-i-built-a-visual-ui-builder-for-react-with-a-json-driven-tree-1af5"&gt;architecture post&lt;/a&gt;, but it's worth restating because it's the kind of decision you'd skip if you were optimizing for code volume.&lt;/p&gt;

&lt;p&gt;Bombie's live preview shows your layout at mobile (~375px), tablet (~768px), and desktop (~1280px) widths. The naive approach is to wrap the preview in a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and set its CSS width to the target value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;375&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Preview&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;em&gt;visually&lt;/em&gt; shrinks the preview, but it lies about responsive behavior. MUI's &lt;code&gt;Grid&lt;/code&gt; uses &lt;code&gt;useMediaQuery&lt;/code&gt; under the hood, which reads &lt;code&gt;window.innerWidth&lt;/code&gt;. The &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is 375px but the &lt;code&gt;window&lt;/code&gt; is still 1280px or whatever your laptop is. &lt;code&gt;xs={12} md={6}&lt;/code&gt; stays at one column because MUI thinks it's on a desktop.&lt;/p&gt;

&lt;p&gt;The fix is an iframe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;previewRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;375px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// inside the iframe: render the preview tree, which now sees its own window object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The iframe has its own &lt;code&gt;window&lt;/code&gt;, its own &lt;code&gt;document&lt;/code&gt;, and its own viewport. &lt;code&gt;useMediaQuery&lt;/code&gt; reads &lt;em&gt;that&lt;/em&gt; window. &lt;code&gt;Grid&lt;/code&gt; breakpoints fire correctly. Hover and focus styles work. Modals (&lt;code&gt;Dialog&lt;/code&gt;) render attached to the iframe's body. Everything you'd want from "what does this look like on mobile" actually behaves like mobile.&lt;/p&gt;

&lt;p&gt;The cost is real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bootstrapping the iframe means injecting the stylesheet and MUI providers (the theme, the cache) into its document.&lt;/li&gt;
&lt;li&gt;Communicating from the outer app to the inner iframe uses &lt;code&gt;postMessage&lt;/code&gt; or a shared module — you can't just pass props across the boundary.&lt;/li&gt;
&lt;li&gt;The iframe takes a few hundred milliseconds to come up on a cold load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Bombie, the iframe receives the JSON tree via &lt;code&gt;postMessage&lt;/code&gt; from the outer toolbar, and the preview-side bootstrap script runs &lt;code&gt;render-preview.js&lt;/code&gt; on whatever tree it most recently received. The MUI cache and theme are injected on first load. Resize is just setting the iframe's &lt;code&gt;width&lt;/code&gt; style.&lt;/p&gt;

&lt;p&gt;I'd absolutely do this again. The honest viewport is worth the setup cost — especially for a tool whose entire pitch is "see what this looks like." Anything that ships responsive previews without an iframe is either accepting a lie or doing a lot of work to fake it with &lt;code&gt;useMediaQuery&lt;/code&gt; overrides; either way the iframe is cheaper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that aren't lessons (yet)
&lt;/h2&gt;

&lt;p&gt;A few things in the repo are still rough and I'm holding off on calling them lessons until I've solved them properly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upload / Download JSON.&lt;/strong&gt; The buttons exist in the toolbar but they're stubs. The serialization story is straightforward (it's &lt;code&gt;JSON.stringify(tree)&lt;/code&gt;), but the user-facing flow — what filenames, what happens on a malformed paste, how to show diff between current state and uploaded state — needs more thought than I've given it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle size.&lt;/strong&gt; MUI is heavy. Bombie's bundle is fine for an experiment but it's not lean. &lt;code&gt;npm run analyze&lt;/code&gt; produces a &lt;code&gt;bundle-report.html&lt;/code&gt; and the obvious targets (date pickers loaded eagerly, icon barrel imports) are still there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript.&lt;/strong&gt; Mentioned in the architecture post. Worth doing, not yet done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these resonate as something you'd like to tackle, the repo's open and PRs are welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up the series
&lt;/h2&gt;

&lt;p&gt;The four posts together cover what Bombie is, how it's structured, how to extend it, and the operational decisions behind it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/amithmoorkoth/introducing-bombie-a-drag-and-drop-builder-for-material-ui-in-react-3d13"&gt;&lt;em&gt;Introducing Bombie&lt;/em&gt;&lt;/a&gt; — what and why&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/amithmoorkoth/how-i-built-a-visual-ui-builder-for-react-with-a-json-driven-tree-1af5"&gt;&lt;em&gt;How I Built a Visual UI Builder for React with a JSON-Driven Tree&lt;/em&gt;&lt;/a&gt; — architecture&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/amithmoorkoth/add-your-own-component-to-bombie-in-5-edits-4oc6"&gt;&lt;em&gt;Add Your Own Component to Bombie in 5 Edits&lt;/em&gt;&lt;/a&gt; — extension tutorial&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Lessons from Building Bombie&lt;/em&gt; — this post&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The underlying ideas — UI as a JSON tree, two renderers from one source of truth, schema-driven property editor, iframe for honest viewports — are reusable past Bombie itself. If you're building any kind of visual editor or layout tool, the JSON-tree-plus-two-renderers split has been the single highest-leverage decision in the codebase.&lt;/p&gt;

&lt;p&gt;Bombie itself is an experiment, not a product. But the patterns are not.&lt;/p&gt;

&lt;p&gt;If you've read this far: &lt;strong&gt;try the &lt;a href="https://bombie-three.vercel.app/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;&lt;/strong&gt;, star the &lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;repo&lt;/a&gt; if it was useful, and open an issue or a PR if you'd like to see something different. The whole point of writing this series was to make the project legible enough that other people could contribute to it.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;https://github.com/amith-moorkoth/bombie&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live demo: &lt;a href="https://bombie-three.vercel.app/" rel="noopener noreferrer"&gt;https://bombie-three.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The series: &lt;a href="https://dev.to/amithmoorkoth/introducing-bombie-a-drag-and-drop-builder-for-material-ui-in-react-3d13"&gt;&lt;em&gt;Introducing Bombie&lt;/em&gt;&lt;/a&gt; · &lt;a href="https://dev.to/amithmoorkoth/how-i-built-a-visual-ui-builder-for-react-with-a-json-driven-tree-1af5"&gt;&lt;em&gt;Architecture&lt;/em&gt;&lt;/a&gt; · &lt;a href="https://dev.to/amithmoorkoth/add-your-own-component-to-bombie-in-5-edits-4oc6"&gt;&lt;em&gt;Add a Component&lt;/em&gt;&lt;/a&gt; · &lt;em&gt;Lessons&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>deployment</category>
    </item>
    <item>
      <title>Add Your Own Component to Bombie in 5 Edits</title>
      <dc:creator>Amith Moorkoth</dc:creator>
      <pubDate>Thu, 21 May 2026 16:54:55 +0000</pubDate>
      <link>https://dev.to/amithmoorkoth/add-your-own-component-to-bombie-in-5-edits-4oc6</link>
      <guid>https://dev.to/amithmoorkoth/add-your-own-component-to-bombie-in-5-edits-4oc6</guid>
      <description>&lt;p&gt;This is the third post in the &lt;strong&gt;&lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;Bombie&lt;/a&gt;&lt;/strong&gt; series. The &lt;a href="https://dev.to/amithmoorkoth/introducing-bombie-a-drag-and-drop-builder-for-material-ui-in-react-3d13"&gt;first post&lt;/a&gt; was an intro, the &lt;a href="https://dev.to/amithmoorkoth/how-i-built-a-visual-ui-builder-for-react-with-a-json-driven-tree-1af5"&gt;second&lt;/a&gt; covered the architecture. This one is a hands-on walkthrough: take a Material-UI component that isn't already in Bombie, and wire it up so it appears in the palette, drags onto the canvas, opens a property editor, and renders in the live preview.&lt;/p&gt;

&lt;p&gt;The whole change is five small edits across the same five files for every new component. Once you've done one, the next one takes about ten minutes.&lt;/p&gt;

&lt;p&gt;I'll use &lt;strong&gt;&lt;code&gt;Rating&lt;/code&gt;&lt;/strong&gt; as the running example. It's a single-leaf component (no children), it has a small but meaningful prop surface (&lt;code&gt;value&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, &lt;code&gt;precision&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;readOnly&lt;/code&gt;, &lt;code&gt;disabled&lt;/code&gt;), and it's not already in the catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Clone and run Bombie locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/amith-moorkoth/bombie.git
&lt;span class="nb"&gt;cd &lt;/span&gt;bombie
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get the builder at &lt;code&gt;http://localhost:8080/generate-component&lt;/code&gt;. Open &lt;code&gt;src/Lib/ComponentGenerator/&lt;/code&gt; — every file you're going to touch lives under there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit 1: register the component in the catalog
&lt;/h2&gt;

&lt;p&gt;The catalog is the source of truth for what Bombie knows about. It lives in two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Data/element-base.js&lt;/code&gt; — display name + tag&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Data/elements.js&lt;/code&gt; — drag-and-drop type info&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add &lt;code&gt;Rating&lt;/code&gt; to &lt;code&gt;element-base.js&lt;/code&gt;:&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="c1"&gt;// src/Lib/ComponentGenerator/Data/element-base.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ELEMENT_BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// …existing entries&lt;/span&gt;
  &lt;span class="na"&gt;Rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rating&lt;/span&gt;&lt;span class="dl"&gt;"&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;And to &lt;code&gt;elements.js&lt;/code&gt;:&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="c1"&gt;// src/Lib/ComponentGenerator/Data/elements.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ELEMENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// …existing entries&lt;/span&gt;
  &lt;span class="na"&gt;Rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;leaf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// it's a leaf, no children&lt;/span&gt;
    &lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;           &lt;span class="c1"&gt;// it doesn't accept any drops&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;&lt;code&gt;type&lt;/code&gt; controls where this component can be dropped (most things accept &lt;code&gt;"leaf"&lt;/code&gt;). &lt;code&gt;accept&lt;/code&gt; is what &lt;em&gt;this&lt;/em&gt; component allows as a child — empty for leaves like Rating, populated for containers like Box / Grid / Stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit 2: write the builder UI file
&lt;/h2&gt;

&lt;p&gt;This is the only new file you'll create. It defines two things: the &lt;strong&gt;schema&lt;/strong&gt; (what the property editor shows) and the &lt;strong&gt;render&lt;/strong&gt; (what the canvas draws).&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;src/Lib/ComponentGenerator/Container/UI/Rating.js&lt;/code&gt;:&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="c1"&gt;// src/Lib/ComponentGenerator/Container/UI/Rating.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rating&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mui/material/Rating&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;makeLeafComponent&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="s2"&gt;./Common/make-component&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;makeLeafComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;select&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;select&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&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="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;min&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="na"&gt;readOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Rating&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;,
&lt;/span&gt;
  &lt;span class="c1"&gt;// Optional defaults applied when the component is dropped on the canvas&lt;/span&gt;
  &lt;span class="na"&gt;defaultProps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&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;A few notes on what's happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;makeLeafComponent&lt;/code&gt; is a factory from &lt;code&gt;UI/Common/make-component.js&lt;/code&gt;. It wraps the renderer with the builder chrome (selection outline, wrench button, delete button) and registers the schema so the property editor knows what controls to show.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema groups&lt;/strong&gt; become sections in the property dialog. "Appearance" gets one card, "State" gets another. This is what makes the editor scannable instead of a wall of inputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field types&lt;/strong&gt; the editor knows about: &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;. Anything else falls through to a text input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;render&lt;/code&gt;&lt;/strong&gt; receives the node's &lt;code&gt;props&lt;/code&gt; (already merged with defaults) and returns plain JSX. No builder logic in this function — that's what the wrapper handles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For container components (Box, Grid, Stack, Card, etc.) you'd use &lt;code&gt;makeContainerComponent&lt;/code&gt; instead. It accepts the same &lt;code&gt;schema&lt;/code&gt; / &lt;code&gt;render&lt;/code&gt; shape but also wires up a &lt;code&gt;&amp;lt;DropBox&amp;gt;&lt;/code&gt; around the children so things can be dropped inside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit 3: register it in the renderer switch
&lt;/h2&gt;

&lt;p&gt;The canvas uses a central registry to know which builder UI file handles which tag. Add Rating to it:&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="c1"&gt;// src/Lib/ComponentGenerator/Container/element-render.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rating&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./UI/Rating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// …other imports&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REGISTRY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// …existing entries&lt;/span&gt;
  &lt;span class="nx"&gt;Rating&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;This is the registry the recursive renderer (&lt;code&gt;element-recursion.js&lt;/code&gt;) consults for every node it walks. &lt;code&gt;node.info.tag&lt;/code&gt; looks up its renderer here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit 4: add an icon + put it in a palette category
&lt;/h2&gt;

&lt;p&gt;The palette in the left sidebar groups components by category. Two small additions:&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="c1"&gt;// src/Lib/ComponentGenerator/Elements/icon-map.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;StarIcon&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mui/icons-material/Star&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// …&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ICON_MAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// …existing entries&lt;/span&gt;
  &lt;span class="na"&gt;Rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StarIcon&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/Lib/ComponentGenerator/Elements/index.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CATEGORIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* … */&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Form Elements&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TextField&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Select&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* …, */&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// add here&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Data Display&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* … */&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;Feedback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* … */&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;Navigation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* … */&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;Categories are just arrays of tags. Pick the one that makes sense — Rating is technically a form input but it's also a display element. I put it under "Form Elements" because it has a &lt;code&gt;value&lt;/code&gt; and a &lt;code&gt;readOnly&lt;/code&gt; mode, but "Data Display" is a defensible choice too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit 5: teach the preview about it
&lt;/h2&gt;

&lt;p&gt;The live preview has its own renderer (&lt;code&gt;render-preview.js&lt;/code&gt;) that emits clean MUI JSX without builder chrome. It uses a switch keyed by tag — add a branch:&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="c1"&gt;// src/Lib/ComponentGenerator/Preview/render-preview.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rating&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mui/material/Rating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// …&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RENDERERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// …existing entries&lt;/span&gt;
  &lt;span class="na"&gt;Rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Rating&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;,
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The preview will now render Rating the same way the canvas does, minus the outline and wrench.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing your change
&lt;/h2&gt;

&lt;p&gt;Hot-reload should pick everything up. Open the builder, find "Rating" in the Form Elements category, drag it onto the canvas. You should see five stars with the third one filled in (your &lt;code&gt;defaultProps.value: 3&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Click the wrench. You should see the property dialog with two sections — "Appearance" with size / max / precision, and "State" with value / readOnly / disabled. Flip "readOnly" to true, change &lt;code&gt;value&lt;/code&gt; to 4.5 (oh wait — precision is 1; set precision to 0.5 first, then 4.5 works).&lt;/p&gt;

&lt;p&gt;Click Preview. The iframe shows your component without any builder chrome. Toggle Mobile / Tablet / Desktop — Rating doesn't have responsive breakpoints itself, but if you'd dropped it inside a Grid it would reflow correctly.&lt;/p&gt;

&lt;p&gt;If something doesn't render: the most common culprit is forgetting Edit 3 (register in &lt;code&gt;element-render.js&lt;/code&gt;) or Edit 5 (register in &lt;code&gt;render-preview.js&lt;/code&gt;). One controls the canvas, the other controls the preview, and the symptoms are different — canvas-only failures mean a missing entry in &lt;code&gt;element-render.js&lt;/code&gt;, preview-only failures mean a missing entry in &lt;code&gt;render-preview.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about container components?
&lt;/h2&gt;

&lt;p&gt;For containers, two things change:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;makeContainerComponent&lt;/code&gt; instead of &lt;code&gt;makeLeafComponent&lt;/code&gt;. It wraps a &lt;code&gt;&amp;lt;DropBox&amp;gt;&lt;/code&gt; around the rendered children.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;elements.js&lt;/code&gt;, set &lt;code&gt;type: "container"&lt;/code&gt; and populate &lt;code&gt;accept&lt;/code&gt; with the tags this container will receive (usually &lt;code&gt;["leaf", "container"]&lt;/code&gt; to allow nesting).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The render function for a container gets &lt;code&gt;children&lt;/code&gt; it should render the canvas-rendered subtree into. Example skeleton:&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="nf"&gt;makeContainerComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyContainer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyContainer&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&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="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/MyContainer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&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;&lt;code&gt;children&lt;/code&gt; here is the already-rendered subtree (with builder chrome on the canvas, plain JSX in the preview).&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on prop validation
&lt;/h2&gt;

&lt;p&gt;Bombie doesn't enforce prop validation beyond what MUI itself does at runtime. If you put &lt;code&gt;value: 99&lt;/code&gt; on a &lt;code&gt;Rating&lt;/code&gt; with &lt;code&gt;max: 5&lt;/code&gt;, MUI will clamp it. The schema's &lt;code&gt;min&lt;/code&gt;/&lt;code&gt;max&lt;/code&gt; hints are advisory — the editor uses them for its number input UI, but the property dialog won't refuse to let you save an out-of-range value.&lt;/p&gt;

&lt;p&gt;In practice this hasn't been a problem because the editor's controls (select dropdowns, numeric inputs with min/max) make it hard to enter nonsense in the first place. But if you're adding a component with strict prop constraints, document them in the schema using a &lt;code&gt;description&lt;/code&gt; field — the property dialog will surface it as helper text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;For any new component, the five edits are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Catalog&lt;/strong&gt; — &lt;code&gt;Data/element-base.js&lt;/code&gt; + &lt;code&gt;Data/elements.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Builder UI&lt;/strong&gt; — one new file under &lt;code&gt;Container/UI/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Renderer registry&lt;/strong&gt; — add to &lt;code&gt;REGISTRY&lt;/code&gt; in &lt;code&gt;element-render.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Palette&lt;/strong&gt; — add icon to &lt;code&gt;icon-map.js&lt;/code&gt; and tag to &lt;code&gt;Elements/index.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview renderer&lt;/strong&gt; — add a branch in &lt;code&gt;render-preview.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's no central manifest file that needs to be regenerated, no build step that needs to run, no test that needs to pass before the new component shows up. Hot-reload picks everything up in seconds.&lt;/p&gt;

&lt;p&gt;The whole architecture is built around making this addition cheap, because the practical value of Bombie scales with how many MUI components it knows about. PRs that add components are exactly the kind of contribution I want.&lt;/p&gt;

&lt;p&gt;In the next post — last in this series — I'll cover the operational lessons: GitHub Pages SPA deep links, the CSP-per-mode setup, and why &lt;code&gt;bombie-three.vercel.app&lt;/code&gt; ended up on Vercel even though the repo has a GitHub Pages workflow.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;https://github.com/amith-moorkoth/bombie&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live demo: &lt;a href="https://bombie-three.vercel.app/" rel="noopener noreferrer"&gt;https://bombie-three.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Previous post: &lt;a href="https://dev.to/amithmoorkoth/how-i-built-a-visual-ui-builder-for-react-with-a-json-driven-tree-1af5"&gt;&lt;em&gt;How I Built a Visual UI Builder for React with a JSON-Driven Tree&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next post: &lt;em&gt;Lessons from Building Bombie: SPA Deep Links, CSP, and an Iframe Preview&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>materialui</category>
    </item>
    <item>
      <title>How I Built a Visual UI Builder for React with a JSON-Driven Tree</title>
      <dc:creator>Amith Moorkoth</dc:creator>
      <pubDate>Thu, 21 May 2026 16:52:15 +0000</pubDate>
      <link>https://dev.to/amithmoorkoth/how-i-built-a-visual-ui-builder-for-react-with-a-json-driven-tree-1af5</link>
      <guid>https://dev.to/amithmoorkoth/how-i-built-a-visual-ui-builder-for-react-with-a-json-driven-tree-1af5</guid>
      <description>&lt;p&gt;This is the second post in the &lt;strong&gt;&lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;Bombie&lt;/a&gt;&lt;/strong&gt; series. The &lt;a href="https://dev.to/amithmoorkoth/introducing-bombie-a-drag-and-drop-builder-for-material-ui-in-react-3d13"&gt;first post&lt;/a&gt; introduced what Bombie is: a drag-and-drop builder for Material-UI in React, with a live demo at &lt;a href="https://bombie-three.vercel.app/" rel="noopener noreferrer"&gt;bombie-three.vercel.app&lt;/a&gt;. This one is about how it's actually put together.&lt;/p&gt;

&lt;p&gt;Bombie's whole design comes from a single decision early on: &lt;strong&gt;represent the UI as a JSON tree, and write two renderers against it&lt;/strong&gt; — one for the builder canvas, one for the live preview. Every other feature is a consequence of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data model
&lt;/h2&gt;

&lt;p&gt;Every node in a Bombie layout has the same shape:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;uuid&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* catalog metadata */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contained&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="cm"&gt;/* nested nodes, same shape */&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;A whole layout is a tree of these. That's it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; — stable identifier; used by the property editor to target a specific node and by &lt;code&gt;react-dnd&lt;/code&gt; to know what's being moved.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;info&lt;/code&gt; — catalog metadata pulled from the element registry. The &lt;code&gt;tag&lt;/code&gt; field (e.g. &lt;code&gt;"Button"&lt;/code&gt;, &lt;code&gt;"Grid"&lt;/code&gt;) is what the renderers switch on.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;props&lt;/code&gt; — the props that will eventually land on the real MUI component. This is what the property editor mutates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;child&lt;/code&gt; — nested nodes. Container components (Box, Grid, Stack, Container, Card, etc.) use this; leaf components (Typography, Button, etc.) leave it empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Saving a layout is &lt;code&gt;JSON.stringify(tree)&lt;/code&gt;. Loading is &lt;code&gt;JSON.parse(json)&lt;/code&gt;. That's the whole persistence story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutating the tree
&lt;/h2&gt;

&lt;p&gt;Mutating a deeply nested immutable tree is the kind of thing that, done naively, spawns three helper files and a Stack Overflow tab. Bombie's lives in one file: &lt;code&gt;src/Lib/Utils/json-handler.js&lt;/code&gt;. It's a collection of recursive walks that take the root tree and the target node's id, and return a new tree.&lt;/p&gt;

&lt;p&gt;The pattern is the same for every operation:&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;function&lt;/span&gt; &lt;span class="nf"&gt;updateNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&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="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;child&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;updateNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;))&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;Everything specializes from there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;setProp(tree, id, key, value)&lt;/code&gt; → &lt;code&gt;updateNode&lt;/code&gt; with a transform that returns &lt;code&gt;{ ...n, props: { ...n.props, [key]: value } }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appendChild(tree, parentId, newNode)&lt;/code&gt; → &lt;code&gt;updateNode&lt;/code&gt; on &lt;code&gt;parentId&lt;/code&gt;, transform adds to &lt;code&gt;child&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;removeNode(tree, id)&lt;/code&gt; → filter &lt;code&gt;child&lt;/code&gt; recursively.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;move(tree, fromId, toParentId, index)&lt;/code&gt; → remove then insert.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the tree is immutable, React's &lt;code&gt;useState&lt;/code&gt; + &lt;code&gt;useMemo&lt;/code&gt; works without any special diffing. The Provider that holds the tree (under &lt;code&gt;Controller/ComponentGenerator/&lt;/code&gt;) hands it down through a context (&lt;code&gt;bombie-context.js&lt;/code&gt;) and exposes these handlers to the canvas, the palette, and the property editor.&lt;/p&gt;

&lt;p&gt;The other utility worth calling out is &lt;code&gt;js-dom-controller.js&lt;/code&gt;. It does dot-path get/set on nested objects — useful when a prop like &lt;code&gt;slotProps.input.startAdornment&lt;/code&gt; needs to be edited from the property dialog. Together, &lt;code&gt;json-handler&lt;/code&gt; (for tree mutations) and &lt;code&gt;js-dom-controller&lt;/code&gt; (for nested prop paths) cover most of the editing surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two renderers, one tree
&lt;/h2&gt;

&lt;p&gt;Here's the design call that pays off the most: Bombie has &lt;strong&gt;two&lt;/strong&gt; renderers for the same tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Builder canvas (&lt;code&gt;element-recursion.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The canvas renders every node with builder chrome: a selection outline, a wrench button (open property editor), a delete button, drop-zone overlays via &lt;code&gt;react-dnd&lt;/code&gt;. Containers render their children recursively as drop targets, so you can nest a &lt;code&gt;Stack&lt;/code&gt; inside a &lt;code&gt;Grid&lt;/code&gt; inside a &lt;code&gt;Box&lt;/code&gt; to whatever depth you want.&lt;/p&gt;

&lt;p&gt;Two complications the canvas has to handle that a clean preview doesn't:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Drop targets need real space.&lt;/strong&gt; An empty &lt;code&gt;Box&lt;/code&gt; would collapse to zero pixels, so the canvas-side container components apply a minimum size and a dashed outline when empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modals are inline.&lt;/strong&gt; A &lt;code&gt;Dialog&lt;/code&gt; in the canvas renders inline (not as an actual modal) so you can edit the dialog's content tree. If it rendered as a real modal, you couldn't see the content unless you opened it — which is fine for previewing but useless for editing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Iframe preview (&lt;code&gt;render-preview.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The preview walks the same tree and emits clean MUI JSX. No outlines, no wrench buttons, no minimum heights. A &lt;code&gt;Dialog&lt;/code&gt; becomes a real &lt;code&gt;&amp;lt;Dialog&amp;gt;&lt;/code&gt; with a generated trigger button: click the trigger, the dialog opens, click any button inside, it closes. That's important because dialog UX is the kind of thing you actually want to feel before you ship it.&lt;/p&gt;

&lt;p&gt;The preview lives inside an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;. Three buttons in the toolbar resize the iframe to common mobile, tablet, and desktop widths. This matters: MUI's &lt;code&gt;Grid&lt;/code&gt; breakpoints (&lt;code&gt;xs&lt;/code&gt;, &lt;code&gt;sm&lt;/code&gt;, &lt;code&gt;md&lt;/code&gt;, &lt;code&gt;lg&lt;/code&gt;, &lt;code&gt;xl&lt;/code&gt;) react to the &lt;strong&gt;window&lt;/strong&gt; they're rendered in. If you just rendered the preview in a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and shrank the div with CSS, MUI would still see the outer browser width and skip the breakpoint behavior entirely. Putting it inside an iframe gives it a real, smaller window — so &lt;code&gt;xs={12} md={6}&lt;/code&gt; actually flips to two columns when you click "Desktop" and back to one when you click "Mobile."&lt;/p&gt;

&lt;p&gt;If you only take one architectural idea from Bombie, take that one. &lt;em&gt;"Use an iframe when you need an honest viewport"&lt;/em&gt; is worth the cost of the iframe.&lt;/p&gt;

&lt;p&gt;The split between the two renderers is enforced by a &lt;code&gt;RENDERERS&lt;/code&gt; switch in &lt;code&gt;Preview/render-preview.js&lt;/code&gt; — one branch per supported component, with &lt;code&gt;props.children&lt;/code&gt; mapped recursively through the same switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The schema-driven property editor
&lt;/h2&gt;

&lt;p&gt;MUI components have a lot of props. &lt;code&gt;Button&lt;/code&gt; alone has &lt;code&gt;variant&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;disabled&lt;/code&gt;, &lt;code&gt;disableElevation&lt;/code&gt;, &lt;code&gt;fullWidth&lt;/code&gt;, &lt;code&gt;href&lt;/code&gt;, &lt;code&gt;startIcon&lt;/code&gt;, &lt;code&gt;endIcon&lt;/code&gt;, and a handful more. A generic property editor that shows them all as labelled text inputs would technically work, but it would be unusable.&lt;/p&gt;

&lt;p&gt;Bombie's editor reads a &lt;strong&gt;schema&lt;/strong&gt; declared next to each component. Schemas group props into sections and give each prop a type — &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt; — that the editor knows how to render. So &lt;code&gt;Button&lt;/code&gt;'s schema groups &lt;code&gt;variant&lt;/code&gt; / &lt;code&gt;color&lt;/code&gt; / &lt;code&gt;size&lt;/code&gt; under "Appearance", &lt;code&gt;disabled&lt;/code&gt; / &lt;code&gt;disableElevation&lt;/code&gt; under "State", and &lt;code&gt;href&lt;/code&gt; / &lt;code&gt;startIcon&lt;/code&gt; / &lt;code&gt;endIcon&lt;/code&gt; under "Behavior", and each field renders as the right control. The dialog adds a section per group, so even a high-cardinality component is navigable.&lt;/p&gt;

&lt;p&gt;Schemas live next to the builder UI files (under &lt;code&gt;Container/UI/&lt;/code&gt;) and are declared with &lt;code&gt;makeLeafComponent&lt;/code&gt; or &lt;code&gt;makeContainerComponent&lt;/code&gt; factories from &lt;code&gt;UI/Common/make-component.js&lt;/code&gt;. The factories pair a &lt;code&gt;schema&lt;/code&gt; with a &lt;code&gt;render&lt;/code&gt; function, and the editor + the canvas read them both.&lt;/p&gt;

&lt;p&gt;I'll cover authoring a new component in detail in the next post — it's about five small edits — but the schema is what makes the editing experience feel deliberate instead of dumped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drag and drop with type guards
&lt;/h2&gt;

&lt;p&gt;Drag-and-drop on the canvas uses &lt;code&gt;react-dnd&lt;/code&gt;. Two small abstractions wrap the primitives: &lt;code&gt;DragBox&lt;/code&gt; (a draggable wrapper, used by the palette and by placed components) and &lt;code&gt;DropBox&lt;/code&gt; (a drop target, used inside container components).&lt;/p&gt;

&lt;p&gt;Each component in the catalog declares &lt;code&gt;type&lt;/code&gt; (what kind of slot it can be dropped into) and &lt;code&gt;accept&lt;/code&gt; (what it accepts as children). The &lt;code&gt;accept&lt;/code&gt; rules prevent, for example, a &lt;code&gt;TextField&lt;/code&gt; from being dropped directly into a &lt;code&gt;Typography&lt;/code&gt;, and prevent layout containers from nesting things that don't make sense. &lt;code&gt;react-dnd&lt;/code&gt;'s &lt;code&gt;useDrag&lt;/code&gt; and &lt;code&gt;useDrop&lt;/code&gt; enforce these at the source — &lt;code&gt;canDrop&lt;/code&gt; returns false and the cursor + outline change.&lt;/p&gt;

&lt;p&gt;The element catalog (the source of truth for &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;accept&lt;/code&gt;) lives in &lt;code&gt;Data/element-base.js&lt;/code&gt; and &lt;code&gt;Data/elements.js&lt;/code&gt;. Adding a new component means adding a row to those — see the next post for the full walkthrough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;The pieces talk to each other through the context provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ComponentGenerator (provider)
├── Elements/        (palette — sources of DragBox)
├── Container/       (canvas — recursive renderer, holds the tree)
│   └── UI/          (per-component builder files + property schemas)
├── Preview/         (iframe renderer for the same tree)
└── Samples/         (pre-built JSON trees that hydrate the canvas)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A drag from the palette dispatches &lt;code&gt;appendChild&lt;/code&gt; on the tree. A click on the wrench opens the property editor, which dispatches &lt;code&gt;setProp&lt;/code&gt; per change. A click on "Preview" passes the current tree through a &lt;code&gt;postMessage&lt;/code&gt; to the iframe, which runs &lt;code&gt;render-preview.js&lt;/code&gt; on it. A click on "Sample → Sign-in card" calls &lt;code&gt;setTree(SAMPLES.signIn)&lt;/code&gt; and the canvas re-renders the whole thing.&lt;/p&gt;

&lt;p&gt;Everything routes through that single tree state. There's no hidden parallel state for the canvas. Whatever the JSON says, that's what's on the screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change if I started over
&lt;/h2&gt;

&lt;p&gt;A few things would be different in a v2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript from the start.&lt;/strong&gt; The JSON-tree shape is dynamic, but the schemas are well-defined enough that they'd benefit from types. Today they're documented by example.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schemas as the source of truth for renderers.&lt;/strong&gt; Right now the schema (props the editor knows about) and the &lt;code&gt;render&lt;/code&gt; function (props the canvas/preview actually uses) are kept in sync by hand. A v2 could derive both from one declaration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tree as a flat map.&lt;/strong&gt; Recursive immutable updates are O(depth) per change. A &lt;code&gt;Record&amp;lt;id, node&amp;gt;&lt;/code&gt; plus a &lt;code&gt;Record&amp;lt;id, childIds&amp;gt;&lt;/code&gt; would let every mutation be O(1).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are urgent — Bombie is small enough that the recursive walks finish in microseconds — but they're the directions a serious version would take.&lt;/p&gt;

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

&lt;p&gt;In the next post I'll walk through adding a brand new component to Bombie end-to-end, covering exactly the five files you need to touch and showing how &lt;code&gt;makeLeafComponent&lt;/code&gt; / &lt;code&gt;makeContainerComponent&lt;/code&gt; keep the additions small. After that, a final post on the operational lessons — SPA deep links on GitHub Pages, the CSP-per-mode setup, and what &lt;code&gt;bombie-three.vercel.app&lt;/code&gt; actually does on a cold load.&lt;/p&gt;

&lt;p&gt;If you want to follow along, the repo is at &lt;strong&gt;&lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;github.com/amith-moorkoth/bombie&lt;/a&gt;&lt;/strong&gt; and PRs / issues are welcome.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;https://github.com/amith-moorkoth/bombie&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live demo: &lt;a href="https://bombie-three.vercel.app/" rel="noopener noreferrer"&gt;https://bombie-three.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Previous post: &lt;a href="https://dev.to/amithmoorkoth/introducing-bombie-a-drag-and-drop-builder-for-material-ui-in-react-3d13"&gt;&lt;em&gt;Introducing Bombie&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next post: &lt;em&gt;Add Your Own Component to Bombie in 5 Edits&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>opensource</category>
      <category>architecture</category>
      <category>materialui</category>
    </item>
    <item>
      <title>Introducing Bombie: A Drag-and-Drop Builder for Material-UI in React</title>
      <dc:creator>Amith Moorkoth</dc:creator>
      <pubDate>Thu, 21 May 2026 16:15:32 +0000</pubDate>
      <link>https://dev.to/amithmoorkoth/introducing-bombie-a-drag-and-drop-builder-for-material-ui-in-react-3d13</link>
      <guid>https://dev.to/amithmoorkoth/introducing-bombie-a-drag-and-drop-builder-for-material-ui-in-react-3d13</guid>
      <description>&lt;p&gt;Most of the time, building a React UI with Material-UI looks like this: open the docs, copy a &lt;code&gt;&amp;lt;Button variant="contained" color="primary"&amp;gt;…&amp;lt;/Button&amp;gt;&lt;/code&gt;, paste it into your component, tweak props, save, alt-tab to the browser, reload, repeat. It works — but for sketching layouts or onboarding new developers to MUI's prop surface, the loop is heavier than it needs to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;Bombie&lt;/a&gt;&lt;/strong&gt; is an experiment in shortening that loop. It's a drag-and-drop visual builder for Material-UI in React. You drop components onto a canvas, edit their props through a grouped property dialog, preview the result at mobile, tablet, or desktop widths, and the whole UI is serialized as a JSON tree under the hood.&lt;/p&gt;

&lt;p&gt;You can try it right now without cloning anything: &lt;strong&gt;&lt;a href="https://bombie-three.vercel.app/" rel="noopener noreferrer"&gt;bombie-three.vercel.app&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is the first in a series. Here I'll cover what Bombie does, who it's for, and the design ideas behind it. Later posts will go into the architecture, how to add a new component in five small edits, and the lessons I picked up shipping it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Bombie actually does
&lt;/h2&gt;

&lt;p&gt;Bombie ships with 40+ draggable Material-UI components organized into five categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Layout&lt;/strong&gt; — &lt;code&gt;Box&lt;/code&gt;, &lt;code&gt;Grid&lt;/code&gt;, &lt;code&gt;Stack&lt;/code&gt;, &lt;code&gt;Container&lt;/code&gt;, dividers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form Elements&lt;/strong&gt; — &lt;code&gt;TextField&lt;/code&gt;, &lt;code&gt;Select&lt;/code&gt;, &lt;code&gt;Checkbox&lt;/code&gt;, &lt;code&gt;Switch&lt;/code&gt;, &lt;code&gt;Slider&lt;/code&gt;, buttons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Display&lt;/strong&gt; — &lt;code&gt;Typography&lt;/code&gt;, &lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;Table&lt;/code&gt;, &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;Avatar&lt;/code&gt;, &lt;code&gt;Chip&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback&lt;/strong&gt; — &lt;code&gt;Alert&lt;/code&gt;, &lt;code&gt;Snackbar&lt;/code&gt;, &lt;code&gt;Dialog&lt;/code&gt;, &lt;code&gt;Progress&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation&lt;/strong&gt; — &lt;code&gt;AppBar&lt;/code&gt;, &lt;code&gt;Tabs&lt;/code&gt;, &lt;code&gt;Breadcrumbs&lt;/code&gt;, &lt;code&gt;Menu&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drag any of them from the palette onto the canvas. Click the wrench icon on a placed component and you get a &lt;strong&gt;grouped property editor&lt;/strong&gt; — variant, color, size, state, behavior — instead of one flat list of inputs. That's the bit I'm most proud of, because MUI's prop surface is wide and a generic "key/value" editor would be miserable to use. Bombie defines a per-component schema and the editor renders form controls from it.&lt;/p&gt;

&lt;p&gt;Switch to &lt;strong&gt;Preview&lt;/strong&gt; and the same tree renders inside an iframe with mobile, tablet, and desktop viewport toggles. Because the breakpoint logic is evaluated against the iframe's actual viewport (not the outer browser), MUI's responsive Grid behavior matches what end-users will see. Dialogs render as real modals in preview mode — Bombie generates a trigger button and an actual &lt;code&gt;&amp;lt;Dialog&amp;gt;&lt;/code&gt;, and clicking any button inside dismisses it. In the builder canvas the dialog body stays flat so you can keep editing it.&lt;/p&gt;

&lt;p&gt;If you want a starting point instead of an empty canvas, there are four &lt;strong&gt;sample templates&lt;/strong&gt; you can load with one click: a sign-in card, a stats dashboard, a settings panel, and an FAQ + support page with an accordion and a dialog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Bombie is for
&lt;/h2&gt;

&lt;p&gt;Bombie isn't trying to replace Figma, and it isn't trying to be a no-code product builder you'd hand to a non-developer for production work. The README is explicit on that point: it's &lt;strong&gt;experimental, not production ready&lt;/strong&gt;, and a couple of toolbar features (Upload JSON / Download JSON) are still placeholders.&lt;/p&gt;

&lt;p&gt;What it's actually useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Learning MUI's prop surface.&lt;/strong&gt; New to Material-UI? Dragging a &lt;code&gt;Button&lt;/code&gt; onto the canvas and flipping through &lt;code&gt;variant&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, and &lt;code&gt;disabled&lt;/code&gt; in a grouped form is faster than reading the docs page for each one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prototyping layouts.&lt;/strong&gt; When you want to see whether a Grid+Card+Typography composition actually responds the way you think, Bombie's iframe preview shows you the truth at three breakpoints in seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demonstrating ideas.&lt;/strong&gt; The JSON serialization means a layout is a single artifact you can paste into an issue or a chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're shipping a real product, keep writing components by hand. If you're exploring, teaching, or making a prototype, Bombie is meant to save you some friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: UI as a JSON tree
&lt;/h2&gt;

&lt;p&gt;The thing that ties all of Bombie's features together is a single representation:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;uuid&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* catalog metadata */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contained&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="cm"&gt;/* nested nodes */&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;Everything else falls out of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The drag-and-drop canvas&lt;/strong&gt; is a recursive renderer over this tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The property editor&lt;/strong&gt; writes back to the &lt;code&gt;props&lt;/code&gt; of the selected node by id.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The live preview&lt;/strong&gt; walks the same tree and emits clean MUI JSX without builder chrome.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save / load&lt;/strong&gt; is just &lt;code&gt;JSON.stringify&lt;/code&gt; and &lt;code&gt;JSON.parse&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll dig into the renderer split — builder vs preview — in the next post in this series, because it's where most of the interesting decisions live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack at a glance
&lt;/h2&gt;

&lt;p&gt;For anyone curious before cloning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React 18 (&lt;code&gt;createRoot&lt;/code&gt; bootstrap)&lt;/li&gt;
&lt;li&gt;Material-UI (MUI)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-dnd&lt;/code&gt; for the drag-and-drop primitives&lt;/li&gt;
&lt;li&gt;Webpack 5 with HMR via &lt;code&gt;webpack-dev-server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Jest for unit + component tests&lt;/li&gt;
&lt;li&gt;ESLint + Prettier + Husky for the dev loop&lt;/li&gt;
&lt;li&gt;Deployed on Vercel; GitHub Pages workflow included as an alternative&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;98% of the code is JavaScript. There's no TypeScript yet — partly because the JSON-tree shape is dynamic enough that strong types would have meant a lot of &lt;code&gt;any&lt;/code&gt; early on, and partly because Bombie started as a personal experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, fork it, break it
&lt;/h2&gt;

&lt;p&gt;The live demo is at &lt;strong&gt;&lt;a href="https://bombie-three.vercel.app/" rel="noopener noreferrer"&gt;bombie-three.vercel.app&lt;/a&gt;&lt;/strong&gt;. The code is at &lt;strong&gt;&lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;github.com/amith-moorkoth/bombie&lt;/a&gt;&lt;/strong&gt; under ISC. To run locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/amith-moorkoth/bombie.git
&lt;span class="nb"&gt;cd &lt;/span&gt;bombie
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The builder lives at &lt;code&gt;http://localhost:8080/generate-component&lt;/code&gt;. On Windows you can double-click &lt;code&gt;run.bat&lt;/code&gt; instead of running the three commands by hand.&lt;/p&gt;

&lt;p&gt;In the next post I'll walk through the architecture: why there are two renderers (one for the builder, one for the preview), how the JSON-tree mutation helpers work, and what the iframe responsive preview actually does under the hood. If you want to follow along, star the repo or drop a comment with what you'd like to see next.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://bombie-three.vercel.app/" rel="noopener noreferrer"&gt;https://bombie-three.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/amith-moorkoth/bombie" rel="noopener noreferrer"&gt;https://github.com/amith-moorkoth/bombie&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next post: &lt;em&gt;How I Built a Visual UI Builder for React with a JSON-Driven Tree&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>materialui</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Mozilla Multiple Page Cross browser PDF Viewer</title>
      <dc:creator>Amith Moorkoth</dc:creator>
      <pubDate>Wed, 26 Apr 2023 17:34:59 +0000</pubDate>
      <link>https://dev.to/amithmoorkoth/mozilla-multiple-page-cross-browser-pdf-viewer-heh</link>
      <guid>https://dev.to/amithmoorkoth/mozilla-multiple-page-cross-browser-pdf-viewer-heh</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/amith-moorkoth/mozilla-pdf-demo"&gt;https://github.com/amith-moorkoth/mozilla-pdf-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mozilla/pdf.js/"&gt;PDF.js&lt;/a&gt; is a Portable Document Format (PDF) viewer that is built with HTML5.&lt;/p&gt;

&lt;p&gt;We have tried to bind multiple pages for the pdf renderer to show for a demo purpose, it is just a few lines of code which extends the above library.&lt;/p&gt;

&lt;p&gt;Please let me know this is interesting, will try to enhance the viewer.&lt;/p&gt;

</description>
      <category>mozilla</category>
      <category>pdf</category>
      <category>javascript</category>
      <category>pdfjs</category>
    </item>
    <item>
      <title>Drag and drop toolkit for React Material UI {custom made}, JSON based react forms Generator.</title>
      <dc:creator>Amith Moorkoth</dc:creator>
      <pubDate>Wed, 15 Mar 2023 14:41:18 +0000</pubDate>
      <link>https://dev.to/amithmoorkoth/drag-and-drop-toolkit-for-react-material-ui-custom-made-json-based-react-forms-generator-2gi2</link>
      <guid>https://dev.to/amithmoorkoth/drag-and-drop-toolkit-for-react-material-ui-custom-made-json-based-react-forms-generator-2gi2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;We are going to discuss about a interesting toolkit that is available for react drag and drop utility. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨 Note: This project is just for demo purpose, cannot be used for production&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Any kind of feedback is welcome, thanks and I hope you enjoy the article.🤗&lt;/p&gt;

&lt;p&gt;👉 Packages used.&lt;br&gt;
▶️ React JS (v.18)&lt;br&gt;
▶️ React DND&lt;br&gt;
▶️ Material UI&lt;/p&gt;

&lt;p&gt;👉 Clone the Project.&lt;br&gt;
   &lt;a href="https://github.com/amith-moorkoth/bombie"&gt;https://github.com/amith-moorkoth/bombie&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 Open Visual Studio Code&lt;br&gt;
  &lt;code&gt;npm install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;👉 For starting the project&lt;br&gt;
  &lt;code&gt;npm start&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;👉 Once the project start running please go to the URL &lt;a href="http://localhost:8080/generate-component"&gt;http://localhost:8080/generate-component&lt;/a&gt; to test the project&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now from here the article is going to be the complete workflow of the react DND bundle, which we have just created now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Assuming that the reader already have prerequisite knowledge about the react, material UI and react DND.&lt;/p&gt;

&lt;h2&gt;
  
  
  👉 Begins &lt;strong&gt;(src\Controller\ComponentGenerator\index.js)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the Controller component where the actual process begins, here we are setting up the context provider for the entire application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yaAmOfRr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/adlkz6okyvg3tw0r8qqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yaAmOfRr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/adlkz6okyvg3tw0r8qqo.png" alt="Image description=&amp;quot;context provider explanation&amp;quot;" width="584" height="72"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  👉 &lt;strong&gt;Bombie Invokes our actual component for processing (src\Lib\ComponentGenerator\index.js)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This component is responsible for drawing the entire screen, which consist of two frames &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Drawer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DND Provider&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K4N4qJ0n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxs6qmkb82o23vqg7oen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K4N4qJ0n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxs6qmkb82o23vqg7oen.png" alt="Image description=&amp;quot;drawing layout consist of drawer and dnd provider&amp;quot;" width="790" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  👉 **Drawer
&lt;/h2&gt;

&lt;p&gt;This is responsible for the showing the JSON data that got generated through the process of drag and drop&lt;/p&gt;

&lt;h2&gt;
  
  
  👉 **DND Provider
&lt;/h2&gt;

&lt;p&gt;This component responsibility includes &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Showing the Element/custom Element that are available for dragging and can be dragged to the drawer for designing a component (src\Lib\ComponentGenerator\DragBox\index.js)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KhGRnLaX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xbexchu313x30i734bg5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KhGRnLaX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xbexchu313x30i734bg5.png" alt="Image description=&amp;quot;all draggable element&amp;quot;" width="465" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draggable Container (src\Lib\ComponentGenerator\Container\element-recursion.js)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DfIHgDHV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59wzgop0ywt2q5f78gpi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DfIHgDHV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59wzgop0ywt2q5f78gpi.png" alt="Image description=&amp;quot;draggable container&amp;quot;" width="691" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a recursive container which will recursively loop and attach the respective DOM, as per the current requirement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🟠 &lt;strong&gt;Why we require JSON based React Material Toolkit? (why the project got started?)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Our Industry from the beginning is very  thirsty about the drag and drop design with functionality includes over it. eg: WordPress, shopify etc...&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;We can store react component as JSON in **mongo db&lt;/strong&gt; and access anywhere by using a single library in react as well as in JavaScript**, this will reduce the big overhead for performance and page loading since we will get the most optimized data which will travel between server and client.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Designers can have the control over the 70% development lifecycle.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...continue&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>materialui</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
