<?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: Subha S. Das</title>
    <description>The latest articles on DEV Community by Subha S. Das (@infolinematrix).</description>
    <link>https://dev.to/infolinematrix</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F793734%2F13878bfc-50b2-49e8-bbe2-1a2f43a6d3a2.png</url>
      <title>DEV Community: Subha S. Das</title>
      <link>https://dev.to/infolinematrix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/infolinematrix"/>
    <language>en</language>
    <item>
      <title>Why I Brought React-Like Hooks to Retui</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Wed, 12 Aug 2026 08:02:15 +0000</pubDate>
      <link>https://dev.to/infolinematrix/why-i-brought-react-like-hooks-to-retui-f3f</link>
      <guid>https://dev.to/infolinematrix/why-i-brought-react-like-hooks-to-retui-f3f</guid>
      <description>&lt;h2&gt;
  
  
  Why I Brought React-Like Hooks to Retui
&lt;/h2&gt;

&lt;p&gt;I didn't set out to copy React. I set out to stop writing terminal apps the way I'd been writing them for years — a giant &lt;code&gt;switch&lt;/code&gt; statement on key events, a pile of mutable struct fields tracking "what screen am I on" and "what's selected right now," and a &lt;code&gt;Draw()&lt;/code&gt; function that touched half of those fields just to figure out what to paint. It worked. It just didn't scale past a few hundred lines without becoming something I was afraid to touch.&lt;/p&gt;

&lt;p&gt;Retui is a terminal UI framework in Go. Components are functions that return an &lt;code&gt;Element&lt;/code&gt;. You describe what the screen should look like given the current state, and the framework figures out what to paint. That part isn't controversial — plenty of frameworks work that way. The part people ask me about is the hooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I actually had
&lt;/h2&gt;

&lt;p&gt;Before hooks, my components needed somewhere to keep state between renders. A text input needs to remember its cursor position. A list needs to remember which row is selected. In a typical Go TUI, you'd put that in a struct — &lt;code&gt;type TextInputModel struct { value string; cursor int }&lt;/code&gt; — and pass it around, or embed it in some bigger app-state struct.&lt;/p&gt;

&lt;p&gt;That's fine for one text input. It stops being fine when you have a form with fifteen fields, three of which are conditionally rendered, and you're trying to keep your app-state struct from turning into a 200-line God object that every component reads and writes.&lt;/p&gt;

&lt;p&gt;React solved a version of this problem with hooks — &lt;code&gt;useState&lt;/code&gt; inside a function component, without you having to define a class or a struct to hold it. I'd used enough React to know that model removes a specific kind of busywork: you stop having to design a state shape up front. You just call &lt;code&gt;UseState&lt;/code&gt; where you need it, and move on.&lt;/p&gt;

&lt;p&gt;So the question wasn't "should I copy hooks." It was "can this idea survive translation into Go, where I don't have closures over component instances the way JSX does, and I don't have a virtual DOM doing reconciliation for me."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tried first
&lt;/h2&gt;

&lt;p&gt;My first instinct was the obvious one: attach state to a component instance. Give every component a stable identity — like React does with the fiber tree — and store its hook state on that instance. This is the "correct" way to do it, and if I were building this in a language with more DOM-like tree diffing built in, I probably would have.&lt;/p&gt;

&lt;p&gt;I got about halfway through building it before I ran into the actual hard part: identity. React can give a component a stable identity across renders because JSX gives you a tree with keys, and the reconciler walks old-tree vs new-tree and matches nodes up. Building that matching logic correctly — handling conditional children, lists, reordering — is most of what makes React's reconciler genuinely complicated software. I didn't want to build a mini React reconciler in Go just to get storage location for a &lt;code&gt;UseState&lt;/code&gt; call. That felt like solving a problem I didn't have yet, in service of an API convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did instead
&lt;/h2&gt;

&lt;p&gt;Retui's render loop calls your component function directly, in order, every frame. There's no tree diffing keeping track of "this Button is the same Button as last render." So instead of attaching hook state to a component instance, I attach it to &lt;em&gt;render order&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Concretely: there's a global hook state slice, and a cursor into it. Every time you call &lt;code&gt;UseState&lt;/code&gt;, &lt;code&gt;UseEffect&lt;/code&gt;, or any other hook, it grabs the next slot in that slice, based on call order, not on which component you're "in." The cursor resets to zero at the start of each render pass. This is really close to how React's own hooks work internally, actually — React also relies on call order within a single component, which is why "don't call hooks conditionally" is a rule there too. I just stretched that same constraint across the whole app instead of scoping it per-component.&lt;/p&gt;

&lt;p&gt;The honest description: hook state in Retui is process-global, not per-component-instance. It's an array, a cursor, and a straightforward set of rules about how you're allowed to call things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this actually works better than it sounds
&lt;/h2&gt;

&lt;p&gt;The first time I explain this to someone, they wince a little, and I get why. "Global state" sounds like the thing you're taught to avoid. But it comes with real upside that I didn't fully appreciate until I'd been using it for a few months.&lt;/p&gt;

&lt;p&gt;It's fast, in a boring, predictable way. There's no reconciliation pass, no tree walk, no key-based matching to figure out which node is which. Getting a hook's value is an array index. That matters more in a terminal renderer than you'd think, because unlike a browser, you're often re-rendering on every single keystroke, and you don't get to hand the expensive part off to native DOM diffing.&lt;/p&gt;

&lt;p&gt;It's also simple to reason about, once you accept the rule. If you've written React, you already half-know the rule: don't call hooks inside conditionals or loops, call them in the same order every render. I didn't invent that constraint — I inherited it, and honestly I think it's a reasonable one to inherit, because it's already proven itself as something developers can learn and live with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I paid for it
&lt;/h2&gt;

&lt;p&gt;I want to be straight about the costs, because I've eaten all of them personally.&lt;/p&gt;

&lt;p&gt;The biggest one: because hook storage is global, Retui can currently only run one app per process. There's no story yet for "run two independent component trees side by side" or "embed a Retui screen inside a larger non-Retui program." For a terminal app that owns the whole screen, this has never actually bitten me — but I know it's a real limitation the moment someone wants to do something more creative with the framework, and I don't have a great answer for them yet.&lt;/p&gt;

&lt;p&gt;The second one is testing. Hook-touching code can't safely run in parallel test cases right now, because they're all reading and writing the same global slice. I've structured the test suite around this — nothing runs &lt;code&gt;t.Parallel()&lt;/code&gt; where hooks are involved — but it's a constraint I have to remember, not one the compiler enforces for me. That's the kind of thing that's fine when I'm the only maintainer keeping it in my head, and less fine as more people contribute.&lt;/p&gt;

&lt;p&gt;There's a smaller, uglier problem too: dynamic lists. If you're rendering &lt;code&gt;UseState&lt;/code&gt; inside a loop — say, one call per row in a table — call-order-based storage breaks, because the list can grow, shrink, or reorder between renders, and "the fifth hook call" stops meaning the same thing. React handles this with &lt;code&gt;key&lt;/code&gt; props on elements, feeding into its reconciler. I don't have a reconciler, so I couldn't reuse that mechanism. I ended up adding a second hook, &lt;code&gt;UseStateKeyed&lt;/code&gt;, that takes an explicit string key instead of relying on position. It works, but it means there are now two ways to keep state, and a developer has to know which one to reach for. That's an API smell I made peace with rather than one I'm proud of. It's the kind of trade-off you make when you're solving a real problem under a self-imposed constraint, and I'd rather have an honest wart than pretend the constraint didn't cost me anything.&lt;/p&gt;

&lt;p&gt;I also learned, a bit painfully, that a hook-order bug — calling &lt;code&gt;UseState&lt;/code&gt; conditionally by accident — can silently reset a piece of state instead of loudly failing. In React that kind of mistake tends to throw. In my current implementation, it can just quietly hand you back the initial value, and you spend twenty minutes wondering why your component "forgot" what the user typed. That's on my list to fix by making it fail loudly in development builds, because a silent bug is so much worse to debug than a panic with a stack trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I think this goes next
&lt;/h2&gt;

&lt;p&gt;I don't think the global-state model is the final form. If Retui grows to the point where people want multiple independent app instances in one process, or safer parallel testing, I'll need to move hook storage onto something scoped to a runtime instance instead of a package-level variable. I know roughly what that refactor looks like, and I know it's a breaking change to the internals, even if the public &lt;code&gt;UseState&lt;/code&gt; call signature barely changes for the end user.&lt;/p&gt;

&lt;p&gt;I haven't done it yet, for one honest reason: nobody has asked for it in a way that told me it was actually blocking them. I'd rather ship a slightly compromised design that real people are using than a theoretically cleaner one that solves a problem nobody has yet. That's a value judgment, not a law of nature, and I could be wrong about the timing.&lt;/p&gt;

&lt;p&gt;What I took from this whole process is a smaller point about borrowing ideas across ecosystems. The part of React worth stealing wasn't the virtual DOM, or the reconciler, or JSX. It was the much simpler idea underneath all of that: let a function remember things across calls without forcing the developer to design a state container up front. That idea is portable. The machinery React built to support it, in a language and runtime shaped very differently from Go, wasn't something I needed to copy wholesale — and trying to copy it exactly would have meant solving problems Go and a terminal renderer don't actually have.&lt;/p&gt;

&lt;p&gt;🔗 Retui: &lt;a href="https://github.com/subhasundardass/retui" rel="noopener noreferrer"&gt;https://github.com/subhasundardass/retui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've ever built a framework, library, or even a complicated internal tool, I'd love to hear about a design decision you're still not completely sure about.&lt;/p&gt;

&lt;p&gt;Those are usually the most interesting ones.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>architecture</category>
      <category>react</category>
    </item>
    <item>
      <title>From an idea to an open-source Go framework: where RetUI is today.</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Fri, 07 Aug 2026 16:17:07 +0000</pubDate>
      <link>https://dev.to/infolinematrix/from-an-idea-to-an-open-source-go-framework-where-retui-is-today-3be7</link>
      <guid>https://dev.to/infolinematrix/from-an-idea-to-an-open-source-go-framework-where-retui-is-today-3be7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxx1v6qnl7x4yiwuquyn3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxx1v6qnl7x4yiwuquyn3.jpg" alt="Open source Go framework Retui" width="800" height="814"&gt;&lt;/a&gt;A few months ago, RetUI was just an idea.&lt;br&gt;
I wanted a different way to build terminal applications in Go—something that could provide a modern, component-based developer experience without losing the simplicity and power of the terminal.&lt;/p&gt;

&lt;p&gt;Since then, RetUI has grown quite a bit.&lt;br&gt;
I've been working on:&lt;/p&gt;

&lt;p&gt;• Component-based UI&lt;br&gt;
• React-inspired hooks and state management&lt;br&gt;
• Flexible, Flexbox-inspired layouts&lt;br&gt;
• Keyboard and focus management&lt;br&gt;
• Screen navigation&lt;br&gt;
• Modals, overlays and toasts&lt;br&gt;
• Reusable form controls&lt;br&gt;
• Tables and other UI components&lt;br&gt;
• Developer tooling and VS Code snippets&lt;/p&gt;

&lt;p&gt;But building the framework is only one part of the journey.&lt;br&gt;
The bigger challenge is making it easy for other developers to discover, understand, use and contribute to it.&lt;br&gt;
That's what I'm focusing on now.&lt;/p&gt;

&lt;p&gt;I'm continuing to improve RetUI, build real applications with it, improve the documentation, and make the developer experience better with every iteration.&lt;/p&gt;

&lt;p&gt;It's still a work in progress—but that's the exciting part of building in public.&lt;/p&gt;

&lt;p&gt;If you're a Go developer:&lt;br&gt;
What would you want to see in a modern terminal UI framework?&lt;br&gt;
I'd genuinely love to hear your ideas.&lt;/p&gt;

&lt;p&gt;⭐ Explore RetUI on GitHub:(&lt;a href="https://github.com/subhasundardass/retui" rel="noopener noreferrer"&gt;https://github.com/subhasundardass/retui&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>terminal</category>
      <category>tui</category>
    </item>
    <item>
      <title>Why I Chose React-Like Hooks in Retui</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Mon, 03 Aug 2026 07:10:16 +0000</pubDate>
      <link>https://dev.to/infolinematrix/why-i-chose-react-like-hooks-in-retui-3clb</link>
      <guid>https://dev.to/infolinematrix/why-i-chose-react-like-hooks-in-retui-3clb</guid>
      <description>&lt;p&gt;When I started building Retui, I had one goal: make terminal application development feel simple and enjoyable.&lt;/p&gt;

&lt;p&gt;As a developer, I have worked with many UI frameworks over the years. One thing I always liked about React was its Hooks. They made it easy to manage state and keep components clean. I wanted to bring the same experience to terminal applications written in Go.&lt;/p&gt;

&lt;p&gt;A Familiar Way to Build&lt;/p&gt;

&lt;p&gt;Many developers already know how React Hooks work.&lt;/p&gt;

&lt;p&gt;Instead of asking them to learn a completely new programming model, Retui lets them write components in a familiar way.&lt;/p&gt;

&lt;p&gt;go&lt;br&gt;
func Counter() retui.Element {&lt;br&gt;
    count, setCount := retui.UseState(0)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return components.Button().
    Label(fmt.Sprintf("Count: %d", count)).
    OnClick(func() {
        setCount(count + 1)
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The code is easy to read. The state stays close to the UI, making it much easier to understand.&lt;/p&gt;

&lt;p&gt;Less Boilerplate&lt;/p&gt;

&lt;p&gt;Without hooks, even a small component often needs extra structs, initialization code, and methods just to store a few values.&lt;/p&gt;

&lt;p&gt;With hooks, I can write the logic in a single function.&lt;/p&gt;

&lt;p&gt;That means less code, fewer files, and less time spent switching between different parts of the project.&lt;/p&gt;

&lt;p&gt;State Belongs to the Component&lt;/p&gt;

&lt;p&gt;Every component should manage its own state whenever possible.&lt;/p&gt;

&lt;p&gt;A text input should know its current value. A checkbox should know whether it is checked. A dialog should know whether it is open.&lt;/p&gt;

&lt;p&gt;Hooks make this natural. Each component owns its own state without requiring a large global state manager.&lt;/p&gt;

&lt;p&gt;Where the State Actually Lives&lt;/p&gt;

&lt;p&gt;Here is the part I go back and forth on the most.&lt;/p&gt;

&lt;p&gt;In React, hook state is attached to the component instance itself. Retui does not work that way. Underneath the API, state is kept in one shared store, and each call to UseState gets its own slot based on the order hooks are called in.&lt;/p&gt;

&lt;p&gt;This was not the plan from day one. I first tried to copy React's approach and attach state to each component instance. It worked, but it meant building something close to a reconciler just to track which instance was which. That was a lot of machinery for a young framework with a small render loop, so I stepped back and went simpler.&lt;/p&gt;

&lt;p&gt;The trade-off is real, and I want to be upfront about it. Because state lives in one shared place, calling the same component twice without a unique key can cause two instances to read and write the same slot. React avoids this because the tree structure gives every instance its own identity for free. Retui does not have that, so the developer has to be a bit more careful, especially with lists of repeated components.&lt;/p&gt;

&lt;p&gt;For that case, Retui has UseStateKeyed, which stores state by a string key instead of call order. It is less automatic, but it is honest about how the state is stored, and it has kept the internals small enough that I can still debug the whole thing in one sitting.&lt;/p&gt;

&lt;p&gt;Easier to Reuse&lt;/p&gt;

&lt;p&gt;Hooks also encourage small, reusable components.&lt;/p&gt;

&lt;p&gt;Instead of creating one large screen with hundreds of lines of code, I can split it into many small components, each with its own state and logic.&lt;/p&gt;

&lt;p&gt;Smaller components are easier to test, maintain, and reuse in other projects.&lt;/p&gt;

&lt;p&gt;Side Effects Are Clear&lt;/p&gt;

&lt;p&gt;Sometimes a component needs to perform an action after rendering.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Load data&lt;br&gt;
Start a timer&lt;br&gt;
Listen for keyboard events&lt;br&gt;
Clean up resources&lt;/p&gt;

&lt;p&gt;UseEffect keeps this logic separate from the UI itself, making the component much easier to follow.&lt;/p&gt;

&lt;p&gt;It Feels Natural&lt;/p&gt;

&lt;p&gt;One of my goals with Retui is to make developers focus on building applications instead of worrying about framework code.&lt;/p&gt;

&lt;p&gt;When I write a Retui component, I want it to feel like writing a normal Go function.&lt;/p&gt;

&lt;p&gt;Hooks help achieve that.&lt;/p&gt;

&lt;p&gt;Is Retui Trying to Copy React?&lt;/p&gt;

&lt;p&gt;Not exactly.&lt;/p&gt;

&lt;p&gt;Retui is written in Go, not JavaScript.&lt;/p&gt;

&lt;p&gt;The rendering system, terminal handling, and internal architecture are completely different.&lt;/p&gt;

&lt;p&gt;What I borrowed is the developer experience.&lt;/p&gt;

&lt;p&gt;React Hooks solved a real problem, and I believe the idea works just as well for terminal applications.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;I didn't add hooks because they are popular.&lt;/p&gt;

&lt;p&gt;I added them because they make code simpler.&lt;/p&gt;

&lt;p&gt;They reduce boilerplate, keep state close to the UI, encourage reusable components, and make applications easier to maintain.&lt;/p&gt;

&lt;p&gt;If you've used React before, Retui will feel familiar.&lt;/p&gt;

&lt;p&gt;If you haven't, you'll probably find that hooks are simply a clean and practical way to build terminal applications.&lt;/p&gt;

&lt;p&gt;Retui is open source. If you want to see how the hooks system is built, or you disagree with a decision I made, the code is on GitHub: &lt;a href="//github.com/subhasundardass/retui"&gt;github.com/subhasundardass/retui&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Tried to Build a UI Framework With No Pixels. Here's What Broke My Brain First.</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Sat, 18 Jul 2026 09:15:16 +0000</pubDate>
      <link>https://dev.to/infolinematrix/i-tried-to-build-a-ui-framework-with-no-pixels-heres-what-broke-my-brain-first-245a</link>
      <guid>https://dev.to/infolinematrix/i-tried-to-build-a-ui-framework-with-no-pixels-heres-what-broke-my-brain-first-245a</guid>
      <description>&lt;p&gt;A few months ago I wanted to build a small command-line tool. Nothing fancy — just a form, a list, maybe a modal dialog. The kind of thing you'd knock out in an afternoon with a modern app framework.&lt;/p&gt;

&lt;p&gt;Then I looked at what "building a UI" actually means in a terminal, in Go.&lt;/p&gt;

&lt;p&gt;It means you get a grid of characters. You decide what character goes in row 12, column 40. You track cursor position by hand. If something changes — say, a counter goes from 4 to 5 — you are responsible for figuring out that only that one character needs to be redrawn, and you write the code to redraw it, or you just repaint the whole screen and accept the flicker.&lt;/p&gt;

&lt;p&gt;I sat there thinking about how modern UI toolkits — component trees, widgets, declarative layout — solved this exact problem years ago on the web and on mobile. Nobody manually paints pixel-by-pixel anymore. You just describe what the UI should look like right now, and something underneath figures out the minimum work to make that true.&lt;/p&gt;

&lt;p&gt;So I asked a question that turned into a much bigger rabbit hole than I expected: could I build that "something underneath" myself, with nothing but 80 columns and 24 rows of monospace text to work with?&lt;/p&gt;

&lt;p&gt;That question is what this article is actually about. Not "look at my project" — but the three ideas that quietly hold together every declarative UI system you've ever used, which I only truly understood once I was forced to build cheap, ugly, terminal-shaped versions of them myself. If you've ever used a state hook or a widget tree without thinking about what it's doing underneath, I think you'll enjoy this.&lt;/p&gt;

&lt;p&gt;(If you want to see where all of this landed, it's an open-source terminal UI framework for Go called retui — link's at the bottom too, no pressure, read on first.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Puzzle #1: Where does component state actually live?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's a question that sounds trivial until you try to implement it: when a component asks for a piece of state and gets back a value plus a setter, where does that value actually live between renders?&lt;/p&gt;

&lt;p&gt;It's not a local variable — those reset every time the component function runs, and it does run again on every render. It's not a class field — you don't need a class for this pattern. So where does it live, and how does the framework know to hand back the fifth update instead of the first?&lt;/p&gt;

&lt;p&gt;The answer, once you build it yourself, is almost anticlimactic: the state lives outside the component entirely, in a plain list, indexed by call order.&lt;/p&gt;

&lt;p&gt;Here's roughly what that looks like when you strip away all the optimizations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;govar&lt;/span&gt; &lt;span class="n"&gt;stateSlots&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stateCursor&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;UseState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stateCursor&lt;/span&gt;
    &lt;span class="n"&gt;stateCursor&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateSlots&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stateSlots&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateSlots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;setState&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newVal&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stateSlots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newVal&lt;/span&gt;
        &lt;span class="n"&gt;requestRerender&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stateSlots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;setState&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before every render, stateCursor resets to zero. Your component function runs top to bottom, and every state call claims the next slot in order — the first call always gets slot 0, the second call always gets slot 1, forever, on every single render.&lt;/p&gt;

&lt;p&gt;This single fact is why "don't call state hooks conditionally" is a rule in basically every framework that uses this pattern. It's not an arbitrary style guideline — it's a direct consequence of how the storage works. If you wrap a state call in an if, you shift every call after it into the wrong slot on renders where the condition is false, and your component starts reading and writing the wrong state entirely. Once you've built the slot array yourself, that rule stops feeling like a rule and starts feeling like simple arithmetic.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Puzzle #2: How do you redraw only what changed, on a grid of text?&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Slot-based state solves storage. It doesn't solve the much scarier problem: once state changes, how do you update the screen without repainting everything?&lt;/p&gt;

&lt;p&gt;Repainting everything is easy, and it's also what full-screen terminal apps did for decades — clear the screen, redraw every line. You'll notice the flicker immediately if you've ever used an old ncurses app over a slow connection.&lt;/p&gt;

&lt;p&gt;The fix is the same one modern UI frameworks landed on, on the web and on mobile: diffing. You render your UI as a tree of lightweight description objects (an "element tree," not the real thing on screen), compare it to the tree from last time, and only touch the parts that differ.&lt;/p&gt;

&lt;p&gt;A minimal version of that idea looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;gofunc&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="kt"&gt;int&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="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;paint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;erase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Style&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Style&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;paint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Children&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;childAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Children&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;childX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;childY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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;Nothing here is clever. It's a recursive tree walk, comparing corresponding nodes and only issuing a paint instruction where something actually differs. What surprised me wasn't the diffing itself — it's that once you have it, an enormous amount of complexity elsewhere in the framework simply disappears. Your component doesn't need to know if it's the first render or the fiftieth. It doesn't need to track "did my text change." It just describes what it wants, every time, and the diffing layer absorbs all the bookkeeping. That's the actual gift declarative UI gave developers, and it turns out to be just as valuable when the "screen" is a grid of monospace characters instead of pixels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Puzzle #3: The bug that taught me the most — events don't know when to stop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's a problem that has nothing to do with rendering and everything to do with a mental model I hadn't questioned before: when a key is pressed, who gets it, and who decides when it's "handled"?&lt;/p&gt;

&lt;p&gt;Say you have a modal dialog open — a settings panel, a confirmation box, whatever — sitting on top of your main screen. You press Escape. Obviously the modal should close. But naively, the same Escape keypress often also reaches whatever's listening underneath, and now your entire app quits too, because your main screen also happens to treat Escape as "exit."&lt;/p&gt;

&lt;p&gt;Every layered UI system needs an answer for this — some way for an inner layer to say "I've dealt with this, don't let it go any further." Most of us have used that mechanism a hundred times without thinking hard about what it's actually preventing. I hadn't, either — not until I had to build the equivalent from scratch, with no framework underneath me to lean on.&lt;/p&gt;

&lt;p&gt;The fix, once you see it, is almost embarrassingly simple: every layer that can handle a key needs to return a plain bool — "did I consume this?" — and every layer above it needs to actually check that value before deciding to do anything itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="n"&gt;gofunc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;HandleKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onKeyPress&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onKeyPress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// true = stop here&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subtle trap is that it's incredibly easy to write this correctly at one layer and then forget to check the return value one layer up — the compiler won't catch it, because ignoring a bool is perfectly legal Go. I did exactly that. My modal closed, and then, one frame later, so did the app underneath it — because the outer event loop was still calling the dispatch function and just throwing away what it returned. The fix was one line. Finding it took an embarrassingly long time, because the bug only shows up on the specific key that both layers care about.&lt;/p&gt;

&lt;p&gt;If there's one thing I'd want a reader to take away from this whole piece, it's that: "stop this event from going any further" isn't a web-specific or mobile-specific idea. It's the general answer to "multiple things want to react to the same event, but only one of them should win," and that problem shows up anywhere you have layered UI — web, terminal, mobile, doesn't matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I think this is worth trying yourself&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;None of this is novel. Every modern declarative UI system — on the web, on mobile, wherever — converges on similar answers, because the underlying problem is the same regardless of what's rendering the pixels (or characters). But there's a real difference between reading that state hooks are "just an array indexed by call order" and being the one who has to make that true, bug by bug, at 11pm, wondering why your fifth state call is returning your third component's data.&lt;/p&gt;

&lt;p&gt;If you've ever wanted a weekend project that will genuinely deepen how you think about a framework you already use every day, I'd recommend picking the smallest possible surface — a todo list, a counter, anything — and building the rendering layer underneath it yourself, with no framework at all. You will hit the exact three puzzles above, in roughly that order, and you'll come out the other side reading your favorite framework's source with completely different eyes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this actually landed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I kept building past the weekend-project point, mostly because Go terminal apps turned out to be genuinely fun to work on. That turned into retui — a small, component-based framework for building terminal UIs in Go, with state hooks, a flexbox-ish layout system, and the diffing/event-propagation machinery described above holding it together underneath.&lt;/p&gt;

&lt;p&gt;🔗 Repo: &lt;a href="https://dev.tourl"&gt;github.com/subhasundardass/retui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's still young, the API still has rough edges, and I'd genuinely welcome anyone poking holes in it, filing issues, or just telling me which part of this post made you go "wait, that's not how I'd have done it" — that's usually where the interesting conversations start.&lt;/p&gt;

&lt;p&gt;What's the last piece of "invisible framework magic" you got curious enough to actually go implement yourself? I'd love to hear about it in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>software</category>
      <category>go</category>
    </item>
    <item>
      <title>Why I Started Building RetUI - A Modern Terminal UI Framework for Go</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Mon, 13 Jul 2026 13:29:02 +0000</pubDate>
      <link>https://dev.to/infolinematrix/why-i-started-building-retui-a-modern-terminal-ui-framework-for-go-l42</link>
      <guid>https://dev.to/infolinematrix/why-i-started-building-retui-a-modern-terminal-ui-framework-for-go-l42</guid>
      <description>&lt;p&gt;As developers, we spend a lot of time building graphical applications for the web and desktop. Yet, some of the most powerful tools we use every day still live in the terminal.&lt;/p&gt;

&lt;p&gt;Over the years, I've used several Go terminal UI libraries. They are powerful and have enabled many great applications. But while building increasingly complex terminal applications, I found myself wanting a different developer experience.&lt;/p&gt;

&lt;p&gt;I wanted to build terminal applications the same way I build modern web applications.&lt;/p&gt;

&lt;p&gt;That's why I started &lt;a href="https://github.com/subhasundardass/retui" rel="noopener noreferrer"&gt;RetUI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;br&gt;
Most terminal UI libraries focus on rendering widgets. They do a great job at that, but as applications grow, developers often end up managing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex layouts&lt;/li&gt;
&lt;li&gt;Keyboard navigation&lt;/li&gt;
&lt;li&gt;Focus management&lt;/li&gt;
&lt;li&gt;Component communication&lt;/li&gt;
&lt;li&gt;Application state&lt;/li&gt;
&lt;li&gt;Window and modal management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As these responsibilities grow, application code can become harder to organize and maintain.&lt;/p&gt;

&lt;p&gt;I wanted a framework that helps solve these problems while keeping the code clean and enjoyable to write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My Vision&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RetUI is inspired by the ideas that made modern frontend development productive.&lt;/p&gt;

&lt;p&gt;I want developers to think in terms of components, not terminal drawing primitives.&lt;/p&gt;

&lt;p&gt;Instead of worrying about how to paint every character on the screen, developers should be able to focus on building their application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design Goals&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RetUI is being built around a few simple principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple and expressive APIs&lt;/li&gt;
&lt;li&gt;Reusable components&lt;/li&gt;
&lt;li&gt;Predictable state management&lt;/li&gt;
&lt;li&gt;Flexible layouts&lt;/li&gt;
&lt;li&gt;Excellent keyboard support&lt;/li&gt;
&lt;li&gt;High performance&lt;/li&gt;
&lt;li&gt;Easy to learn&lt;/li&gt;
&lt;li&gt;Easy to extend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why Another Framework?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn't about replacing existing Go TUI libraries.&lt;/p&gt;

&lt;p&gt;The Go ecosystem already has excellent projects, and I've learned a lot from them.&lt;/p&gt;

&lt;p&gt;RetUI explores a different direction—bringing a more component-driven development style to terminal applications while remaining lightweight and idiomatic in Go.&lt;/p&gt;

&lt;p&gt;If this approach helps even a small group of developers build better terminal applications, then the project will have achieved its purpose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Journey&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RetUI is still in its early stages.&lt;/p&gt;

&lt;p&gt;There will be bugs.&lt;br&gt;
There will be redesigns.&lt;br&gt;
Some APIs will change.&lt;/p&gt;

&lt;p&gt;That's part of building software.&lt;/p&gt;

&lt;p&gt;I'm sharing the project early because I believe open-source software grows stronger through feedback and collaboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Join Me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're interested in terminal applications, Go, or developer tooling, I'd love to hear your thoughts.&lt;/p&gt;

&lt;p&gt;Whether it's reporting bugs, suggesting ideas, improving documentation, or contributing code, every bit of feedback helps.&lt;/p&gt;

&lt;p&gt;Let's see how far we can push terminal applications with Go.&lt;/p&gt;

&lt;p&gt;This is just the beginning of the RetUI journey.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/subhasundardass" rel="noopener noreferrer"&gt;
        subhasundardass
      &lt;/a&gt; / &lt;a href="https://github.com/subhasundardass/retui" rel="noopener noreferrer"&gt;
        retui
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      RETUI is a lightweight, component-driven framework for creating beautiful, interactive Terminal User Interfaces (TUIs) in Go.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;retui&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A Go framework for building interactive terminal UIs with React-style components and hooks.&lt;/p&gt;
&lt;p&gt;Inspired by React and Flutter, retui brings a component-based, reactive approach to building terminal applications — write functional components, manage state with hooks, and let a flexbox layout engine handle the rest.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/subhasundardass/retui/retui_banner.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsubhasundardass%2Fretui%2FHEAD%2Fretui_banner.png" alt="Retui Framework" width="700"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/subhasundardass/retui#features" rel="noopener noreferrer"&gt;Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/subhasundardass/retui#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/subhasundardass/retui#quick-start" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/subhasundardass/retui#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/subhasundardass/retui#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Functional components&lt;/strong&gt; — plain Go functions that return an &lt;code&gt;Element&lt;/code&gt; tree&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt; — &lt;a href="https://github.com/subhasundardass/retui/DOCS.md#usestate" rel="noopener noreferrer"&gt;&lt;code&gt;UseState&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/subhasundardass/retui/DOCS.md#useeffect" rel="noopener noreferrer"&gt;&lt;code&gt;UseEffect&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/subhasundardass/retui/DOCS.md#usecontext" rel="noopener noreferrer"&gt;&lt;code&gt;UseContext&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexbox &lt;a href="https://github.com/subhasundardass/retui/DOCS.md#layout" rel="noopener noreferrer"&gt;layout engine&lt;/a&gt;&lt;/strong&gt; — two-pass (measure → layout) with &lt;code&gt;Row&lt;/code&gt;/&lt;code&gt;Column&lt;/code&gt; direction, &lt;code&gt;Gap&lt;/code&gt;, &lt;code&gt;Padding&lt;/code&gt;, &lt;code&gt;Align&lt;/code&gt;, &lt;code&gt;Justify&lt;/code&gt;, and &lt;code&gt;Fixed&lt;/code&gt;/&lt;code&gt;Grow&lt;/code&gt;/&lt;code&gt;Fit&lt;/code&gt; sizing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich &lt;a href="https://github.com/subhasundardass/retui/DOCS.md#styling" rel="noopener noreferrer"&gt;styling&lt;/a&gt;&lt;/strong&gt; — ANSI16, ANSI256, and RGB/Hex colors; bold, italic, underline; four border presets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bracketed paste&lt;/strong&gt; — multi-line clipboard content arrives as a single &lt;code&gt;KeyPaste&lt;/code&gt; event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in &lt;a href="https://github.com/subhasundardass/retui/DOCS.md#component-library" rel="noopener noreferrer"&gt;component library&lt;/a&gt;&lt;/strong&gt; — Table, Tabs, Modal, Input, Button, Checkbox, List, SelectPicker, Spinner, ProgressBar, Alert, Badge, Panel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient rendering&lt;/strong&gt; — cell-level…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/subhasundardass/retui" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>go</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Building a Window Management System for Terminal UIs</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:33:36 +0000</pubDate>
      <link>https://dev.to/infolinematrix/building-a-window-management-system-for-terminal-uis-3e4o</link>
      <guid>https://dev.to/infolinematrix/building-a-window-management-system-for-terminal-uis-3e4o</guid>
      <description>&lt;p&gt;Terminal User Interfaces (TUIs) have come a long way from simple menus and forms. Modern TUI applications increasingly need sophisticated UI paradigms—multiple panels, popups, dialogs, and floating windows. However, building a robust window management system from scratch presents unique challenges in the terminal environment.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through the architecture, design decisions, and challenges faced while building a complete window management system for TUI applications. The full source lives on GitHub at &lt;a href="https://github.com/subhasundardass/tuix" rel="noopener noreferrer"&gt;subhasundardass/tuix&lt;/a&gt;, if you'd rather read the real code than just the snippets below. The code we'll discuss provides a production-ready window manager with features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Floating windows with absolute positioning&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modal windows that block background interaction&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Z-order management (window stacking)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus management with keyboard navigation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fluent API for easy window configuration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thread-safe operations&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Build a Window System for TUIs?
&lt;/h2&gt;

&lt;p&gt;I didn't set out to build a window manager. I set out to add a "confirm delete" dialog to a CLI tool, and about three days later I had a hundred-line rabbit hole on my hands.&lt;/p&gt;

&lt;p&gt;Here's the thing nobody tells you when you start writing TUIs: the terminal was never designed for overlapping surfaces. A GUI toolkit gives you compositing, z-buffers, and a windowing system for free — the OS handles it. In a terminal, you get a grid of cells and a cursor. That's it. If you want one panel to visually sit "on top of" another, you have to figure out what that even means when both are just characters being written to the same 2D buffer.&lt;/p&gt;

&lt;p&gt;Most TUI libraries sidestep this by giving you a single, fixed layout — a tree of panes that split the screen and never overlap. That's fine until your app actually needs a popup. A confirmation dialog. A command palette. An autocomplete dropdown that has to float above whatever's underneath it without destroying that content. At that point, you're not laying out boxes anymore — you're managing a window system, whether you meant to or not.&lt;/p&gt;

&lt;p&gt;So that's what I built. Not because it was the fun weekend project I was planning, but because every alternative I tried (special-casing "just this one popup," hacking a second screen buffer, praying) fell apart the second I needed a &lt;em&gt;second&lt;/em&gt; popup on top of the first one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem: Everything Shares One Buffer
&lt;/h2&gt;

&lt;p&gt;In a real windowing system, each window typically owns its own surface, and the compositor merges them at the end. Terminals don't give you that. Every widget, every panel, every floating dialog is ultimately writing characters into the same flat buffer that gets flushed to the screen.&lt;/p&gt;

&lt;p&gt;That means a "window manager" for a TUI isn't really managing windows in the OS sense — it's managing &lt;em&gt;draw order&lt;/em&gt; and &lt;em&gt;input routing&lt;/em&gt; against a shared canvas. Two problems fall out of that immediately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rendering order matters enormously.&lt;/strong&gt; If window B is on top of window A and they overlap, B has to be drawn &lt;em&gt;after&lt;/em&gt; A, cell by cell, or you get visual garbage — characters from the wrong window bleeding through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input has to be routed, not broadcast.&lt;/strong&gt; A keypress can only go to one place at a time (or, for modals, be deliberately blocked from going anywhere else). Figure out who "owns" the terminal at any given moment, and you've solved half the problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else in the system — z-ordering, focus, modality — is really just infrastructure built to answer those two questions cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: Windows as a Managed Stack
&lt;/h2&gt;

&lt;p&gt;The design I landed on treats every floating surface as a &lt;code&gt;Window&lt;/code&gt; struct with a handful of properties: position, size, content, a z-index, and some behavioral flags (is it modal, is it focusable, is it currently visible). The &lt;code&gt;WindowManager&lt;/code&gt; owns a slice of these, sorted by z-index, and it's the single source of truth for "what's currently on screen and in what order."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Window&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;        &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Width&lt;/span&gt;       &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Height&lt;/span&gt;      &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;ZIndex&lt;/span&gt;      &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Modal&lt;/span&gt;       &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Focusable&lt;/span&gt;   &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Visible&lt;/span&gt;     &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;     &lt;span class="n"&gt;Drawable&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;WindowManager&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;       &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
    &lt;span class="n"&gt;windows&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Window&lt;/span&gt;
    &lt;span class="n"&gt;focused&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;nextZ&lt;/span&gt;    &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;mu sync.RWMutex&lt;/code&gt; up there isn't decoration. TUI apps that pull data from network calls or background jobs will very often want to pop up a "loading" window or update content from a goroutine, and the moment you have concurrent writers touching the window stack, you need real locking — not a "well it's probably fine" shrug. More on that later.&lt;/p&gt;

&lt;p&gt;Every operation that changes what's visible — opening a window, closing one, bringing one to front — goes through the manager rather than mutating window state directly. That single choke point is what makes the rest of the system tractable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Floating Windows and Absolute Positioning
&lt;/h2&gt;

&lt;p&gt;The base layout system in most TUI frameworks works in relative terms: split the screen, size things as a fraction of their container, let the framework do the arithmetic. Floating windows break that model on purpose — they need to say "put me at row 10, column 20, and I don't care what else is on screen."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WindowManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nextZ&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nextZ&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;focused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Positioning itself is simple math — clamp X/Y so the window doesn't render off the edge of the terminal, clamp width/height so it doesn't exceed the available rows and columns. The part that's &lt;em&gt;not&lt;/em&gt; simple is what happens when two floating windows overlap, which is really a rendering problem more than a positioning one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering: Painter's Algorithm, Terminal Edition
&lt;/h2&gt;

&lt;p&gt;Once windows are sorted by z-index, rendering is conceptually just the painter's algorithm from computer graphics: draw the bottom-most window first, then paint each subsequent window on top, letting later windows overwrite earlier ones cell by cell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WindowManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZIndex&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZIndex&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;sorted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DrawInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&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;The subtlety is in &lt;code&gt;DrawInto&lt;/code&gt;. It's not enough to write a window's characters into the buffer — you also have to write its &lt;em&gt;background&lt;/em&gt;, including a drop shadow or border if the window has one, so it visually reads as "in front of" whatever it's covering rather than looking like it's been stamped through the layer below. I ended up drawing a one-cell shadow offset to the bottom-right of every floating window; it's a small touch, but it's the single biggest thing that made the UI feel like actual overlapping windows instead of just "text with holes in it."&lt;/p&gt;

&lt;h2&gt;
  
  
  Modal Windows: Blocking on Purpose
&lt;/h2&gt;

&lt;p&gt;Modals are conceptually simple — a window that owns input exclusively until it's dismissed — but they expose a design question that's easy to get wrong: does "modal" live on the window, or on the manager?&lt;/p&gt;

&lt;p&gt;I put it on the manager, computed from the window stack, rather than as a standalone flag anywhere else in the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WindowManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;TopModal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Modal&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WindowManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;HandleInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt; &lt;span class="n"&gt;InputEvent&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="n"&gt;modal&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TopModal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;modal&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;modal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;focused&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FocusedWindow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;focused&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;focused&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&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;That "top-most modal wins, full stop" rule matters because you'll eventually stack modals — a confirmation dialog spawned from within another dialog is a completely normal thing for users to trigger, and if your modal logic isn't stack-aware, the second dialog either won't block correctly or will block the wrong thing. Deriving modality from the stack, instead of tracking "is anything modal right now" as separate boolean state, means it's correct by construction no matter how deep the stack gets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Focus Management and Keyboard Navigation
&lt;/h2&gt;

&lt;p&gt;Mouse support in terminals is inconsistent enough across emulators that keyboard navigation isn't optional — it's the primary interface for a meaningful chunk of your users. That makes focus tracking one of the more important pieces of the whole system, even though it looks like the most boring one.&lt;/p&gt;

&lt;p&gt;The rule I settled on: focus always follows the top of the z-stack unless the user explicitly moves it (Tab / Shift+Tab between focusable windows, or clicking a window if the terminal supports mouse events). Opening a new window focuses it automatically; closing a window returns focus to whatever is now on top.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WindowManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CycleFocus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;focusable&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;focusableWindows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&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="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;focused&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;focused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;
    &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BringToFront&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&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;One detail that took a couple of iterations to get right: bringing a window to front on focus is usually what you want, &lt;em&gt;except&lt;/em&gt; for background utility windows — a status bar, a log panel — that shouldn't jump on top of a modal just because they briefly held focus. I ended up adding a &lt;code&gt;Sticky&lt;/code&gt; flag for exactly that case, which excludes a window from z-reordering even while letting it stay focusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fluent API
&lt;/h2&gt;

&lt;p&gt;None of the above is much fun to configure by hand every time you want a dialog. So the actual thing most consumers of this library touch is a builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"confirm-delete"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Confirm Delete"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;confirmDialog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;OnClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dialog dismissed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&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;Position(Center, Center)&lt;/code&gt; deserves a mention — it's resolved lazily, against the current terminal dimensions, at the moment the window is opened rather than when it's built. That matters because terminals resize, and a window built once but reused across multiple opens (a settings dialog, say) needs to re-center correctly every time, not just the first.&lt;/p&gt;

&lt;p&gt;The fluent style isn't just sugar. It front-loads validation — width/height bounds, ID collisions, invalid position combos — into the builder, so &lt;code&gt;Open()&lt;/code&gt; can assume it's always handed a well-formed window instead of defensively checking everything at render time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thread Safety: The Part That Bites You Later
&lt;/h2&gt;

&lt;p&gt;I'll be honest — the first version of this didn't have a mutex anywhere. It worked fine, right up until I wired in an async data fetch that wanted to close a "loading" window and open a "results" window from a goroutine while the main loop was mid-render. The result was a window stack that occasionally rendered with a window half-removed — not a crash, just silent visual corruption, which is a much worse bug to track down.&lt;/p&gt;

&lt;p&gt;The fix is the &lt;code&gt;sync.RWMutex&lt;/code&gt; from earlier, applied consistently: every mutation takes the write lock, every read (including the render pass) takes the read lock. The one rule that took discipline to enforce was making sure &lt;code&gt;Content.HandleInput&lt;/code&gt; and &lt;code&gt;Content.DrawInto&lt;/code&gt; — the callbacks into user-supplied window content — never get called &lt;em&gt;while&lt;/em&gt; the manager's lock is held, since a badly behaved callback that tries to open another window from inside its own draw call would deadlock instantly. Snapshotting the sorted window list before iterating, as in the &lt;code&gt;Render&lt;/code&gt; function above, solves that cleanly: the lock only needs to cover the bookkeeping, not the actual drawing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;A few things I'd change if I were starting over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dirty-rect tracking.&lt;/strong&gt; Right now every render redraws the full stack from scratch. It's fast enough for the window counts a typical TUI app actually has, but it's wasteful, and a diff-based redraw would matter a lot for anything with animation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resize events per-window.&lt;/strong&gt; Terminal resizes currently trigger a full re-layout pass across every open window. Most windows don't need to know or care that the terminal resized unless they're centered or edge-anchored; a subscription model would cut a lot of unnecessary work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better shadow/border theming earlier.&lt;/strong&gt; I treated visual polish as a late-stage concern and regretted it — the shadow-offset trick that made floating windows &lt;em&gt;read&lt;/em&gt; as floating was worth doing on day one, not day twelve.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The terminal is a genuinely constrained environment, and it's easy to underestimate how much of "real" windowing — layering, focus, modality — you end up reimplementing the moment your TUI needs more than a single static layout. None of the individual pieces here are exotic; it's mostly careful bookkeeping around a shared buffer and a very literal notion of "on top." But that bookkeeping is exactly the part that's tedious to get right under deadline pressure, which is usually when people reach for a shortcut and end up with popups that flicker, dialogs that don't block correctly, or focus that silently gets lost. Getting the primitives right once, and building everything else on top of them, turned out to be worth the three days it wasn't supposed to take.&lt;/p&gt;

&lt;p&gt;If you want to dig into the actual implementation, run it yourself, or file an issue about something I got wrong, the repo is at &lt;strong&gt;&lt;a href="https://github.com/subhasundardass/tuix" rel="noopener noreferrer"&gt;github.com/subhasundardass/tuix&lt;/a&gt;&lt;/strong&gt;. Stars, forks, and PRs are all welcome — and if you build something with it, I'd love to hear about it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Not every UI needs a browser.</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Fri, 19 Jun 2026 08:29:00 +0000</pubDate>
      <link>https://dev.to/infolinematrix/not-every-ui-needs-a-browser-3jik</link>
      <guid>https://dev.to/infolinematrix/not-every-ui-needs-a-browser-3jik</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhyg1105qsc1g9tul4mxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhyg1105qsc1g9tul4mxf.png" alt=" " width="800" height="528"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Building a Dashboard-Style Terminal UI (TUI) in Go 🚀&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As developers, we often default to web applications and desktop GUIs. But sometimes, a Terminal User Interface (TUI) can be a powerful alternative—especially for internal tools, dashboards, DevOps utilities, and ERP systems.&lt;/p&gt;

&lt;p&gt;Over the last few days, I've been experimenting with building a dashboard-style TUI in Go using the tview library. The goal wasn't just to display text in a terminal, but to create an experience similar to a modern admin dashboard:&lt;/p&gt;

&lt;p&gt;✅ Sidebar navigation with nested menus&lt;br&gt;
✅ Dashboard KPI cards (Users, Revenue, Orders, Products)&lt;br&gt;
✅ ERP-style data tables with proper alignment and financial columns&lt;br&gt;
✅ Dynamic page switching and screen management&lt;br&gt;
✅ Keyboard-driven navigation and focus handling&lt;br&gt;
✅ Modular architecture with reusable components&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why a TUI?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A terminal application offers some interesting advantages:&lt;/p&gt;

&lt;p&gt;Lightweight: No browser or heavy runtime required.&lt;br&gt;
Fast: Minimal resource consumption and quick startup.&lt;br&gt;
Cross-platform: Build once and run almost anywhere.&lt;br&gt;
Keyboard-first workflow: Extremely productive for power users.&lt;br&gt;
Ideal for internal systems: Monitoring tools, administration panels, DevOps dashboards, and enterprise applications.&lt;br&gt;
Architecture Approach&lt;/p&gt;

&lt;p&gt;Instead of placing all UI logic in one file, I designed the application with a modular structure:&lt;/p&gt;

&lt;p&gt;Application&lt;br&gt;
├── Bootstrap&lt;br&gt;
├── Screen Manager&lt;br&gt;
├── Layout Manager&lt;br&gt;
│   ├── Header&lt;br&gt;
│   ├── Sidebar&lt;br&gt;
│   ├── Content Pages&lt;br&gt;
│   └── Footer&lt;br&gt;
├── Screens&lt;br&gt;
│   ├── Home&lt;br&gt;
│   ├── Orders&lt;br&gt;
│   └── Settings&lt;br&gt;
└── Shared Components&lt;/p&gt;

&lt;p&gt;The Screen Manager handles page registration and navigation, while the Layout Manager keeps global components such as the sidebar and header persistent.&lt;/p&gt;

&lt;p&gt;Challenges I Encountered&lt;/p&gt;

&lt;p&gt;Building a dashboard in a terminal is surprisingly different from building one on the web:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Focus Management&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Managing keyboard focus between the sidebar and page content required careful handling. Determining which component should own focus at any given moment is crucial for a good user experience.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Table Alignment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unlike HTML tables, tview.Table doesn't provide CSS-like control. Proper alignment of financial data (Amount, Tax, Discount, Total) required manual configuration and thoughtful layout design.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dashboard Density&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Creating information-dense dashboards in a terminal is challenging because terminal UIs have limited styling capabilities compared to modern web frameworks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Component Architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Separating screens, layouts, navigation, and shared widgets became essential to keep the application maintainable as it grew.&lt;/p&gt;

&lt;p&gt;What I Learned&lt;/p&gt;

&lt;p&gt;Terminal applications are not just nostalgic throwbacks. They can still be highly effective for:&lt;/p&gt;

&lt;p&gt;ERP systems&lt;br&gt;
Administrative dashboards&lt;br&gt;
Monitoring tools&lt;br&gt;
DevOps platforms&lt;br&gt;
Internal business applications&lt;br&gt;
Data management systems&lt;/p&gt;

&lt;p&gt;Go's simplicity, performance, and excellent libraries such as tview and tcell make it an enjoyable ecosystem for building these kinds of applications.&lt;/p&gt;

&lt;p&gt;What's Next?&lt;/p&gt;

&lt;p&gt;I'm planning to explore:&lt;/p&gt;

&lt;p&gt;Dynamic widgets and plugin architecture&lt;br&gt;
Better table experiences (sorting, filtering, pagination)&lt;br&gt;
Modal windows and drill-down screens&lt;br&gt;
Event-driven screen communication&lt;br&gt;
More condensed and information-rich layouts&lt;/p&gt;

&lt;p&gt;Building this project reminded me that great software doesn't always require a browser. Sometimes, a well-designed terminal interface can be remarkably productive and elegant.&lt;/p&gt;

&lt;p&gt;Have you built a Terminal UI application before? Which language or framework would you choose for it?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Odoo Customisation &amp; Module Development</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Thu, 05 Mar 2026 10:51:52 +0000</pubDate>
      <link>https://dev.to/infolinematrix/odoo-customisation-module-development-1ih8</link>
      <guid>https://dev.to/infolinematrix/odoo-customisation-module-development-1ih8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffcau9mf3a88x4sie3zgg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffcau9mf3a88x4sie3zgg.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Custom Odoo modules, workflow enhancements, and integrations aligned with your operations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Odoo is one of the most powerful open-source business management platforms available — covering CRM, accounting, inventory, HR, manufacturing, e-commerce, and more. But out of the box, Odoo is built for the average business. Your business isn't average. Your business isn’t average.&lt;/p&gt;

&lt;p&gt;Odoo customisation means modifying or extending Odoo to match your exact operational requirements — from small UI adjustments and custom fields, all the way to fully bespoke modules that add entirely new functionality. As the leading Odoo expert in Siliguri, we work with businesses across North Bengal and globally, helping them unlock Odoo's true potential through smart, purposeful customisation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dorii.in/service/odoo-customisation" rel="noopener noreferrer"&gt;read more&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>python</category>
    </item>
    <item>
      <title>Discover how a Transport Management System (TMS) helps Indian transport businesses save time, reduce costs, and manage deliveries and fleets easily.

read more at https://dorii.in/blog/what-is-tms</title>
      <dc:creator>Subha S. Das</dc:creator>
      <pubDate>Tue, 10 Feb 2026 13:22:22 +0000</pubDate>
      <link>https://dev.to/infolinematrix/discover-how-a-transport-management-system-tms-helps-indian-transport-businesses-save-time-4l5a</link>
      <guid>https://dev.to/infolinematrix/discover-how-a-transport-management-system-tms-helps-indian-transport-businesses-save-time-4l5a</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dorii.in/blog/what-is-tms" rel="noopener noreferrer" class="c-link"&gt;
            What Is a Transport Management System (TMS)? Complete Guide | Dorii Software
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Discover how a Transport Management System (TMS) helps Indian transport businesses save time, reduce costs, and manage deliveries and fleets easily.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdorii.in%2Ffavicon.ico" width="" height=""&gt;
          dorii.in
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
  </channel>
</rss>
