@opentui/react
0.6.8 · 23 Oct 2026MIT225.5k / weekRepositoryHomepagenpmAtom feed
Latest releases
All 156 releases- 0.6.x · latest line
0.6.8
23 Oct 2026web: format the 0.6.7 release notes
native: move the single-input width tests into the text corpus
Fixed
core: A left or right wheel step no longer shifts a
TextorCodewithwrapMode: "none"andtruncate: trueby one column. A text with a sideways offset now returns to column 0 whentruncatebecomestrueorwrapModechanges to"word"or"char".
API changes and documentation: opentui.com/docs/releases/0.6.8
What's Changed
web: format the 0.6.7 release notes
native: move the single-input width tests into the text corpus
core: ignore sideways scroll on truncated text
0.6.7
23 Oct 2026- breaking
core: A Box with a default-ignorable character in
customBorderChars, such as U+200E or U+FFA0, throwsInvalidArgumentat creation, anddrawGridrejects such a character. Use a border character that takes one cell. See Box. core: The
gutteroption and property ofDiffRenderablesetpaddingLeft,paddingRight, andminDigitsfor the line-number gutter. Diffs with the sameminDigitsalign their code columns. See Diff.core:
LineNumberRenderable.getGutterLayout()andsetGutterLayout()read and set the gutter padding, minimum digits, and minimum widths as aGutterLayout. See Line number gutter.
In 0.6.6, OpenTUI gave the text
a\u200Ebcdefseven cells, but the terminal drew it in six. U+200E, the left-to-right mark, is one of Unicode's default-ignorable code points, and terminals draw these in no cell. OpenTUI gave each of them one cell. As a result, it placed the rest of the row one column right of where the terminal drew it. A full frame looked correct, but the next partial update did not: in Ghostty, changing theftoZshowedabcdefZ. Hebrew and Arabic text often carries direction marks, so these rows broke when they were updated. In 0.6.7, every default-ignorable code point takes no cell. Layout, cursor movement, drawing, andencodeUnicodeuse the same width, so they agree. Two exceptions remain. The soft hyphen U+00AD keeps one cell, because terminals draw it. The conjoining Hangul fillers U+115F and U+1160 keep their widths, because each starts a syllable cluster.When more code points have zero width, an editor bug becomes more common. In 0.6.6, a Textarea line that started with U+200B trapped the cursor. Right did not move past it from column 0, and Delete did nothing there. No key could delete a line that held only zero-width text. Without a fix, a Hebrew line that starts with U+200F would act the same way. 0.6.7 fixes both cases. A border character must still fill exactly one cell, so a
customBorderCharsentry such as U+200E or U+FFA0 is now an error. Before, it gave a misaligned border. Replace it with a character that takes one cell, such as a space.A terminal reports mouse motion only when the pointer enters a new cell. In 0.6.6, the renderer gave a left-button drag to the renderable under the first motion report. A fast drag on a one-column ScrollBox scrollbar thumb often reported its first motion over a neighbor. The bar then did not scroll, and the thumb kept its dragging state. Now the renderable that receives the left press owns the drag. If the press hit nothing, or its renderable was destroyed, the renderable under the pointer is the source, as before. A press that starts or extends a text selection still follows the selection path.
registerBaseLayoutFallback()is for keyboard layouts that cannot type the character of a binding. On a Russian layout, Ctrl+с must still runctrl+c. In 0.6.6, the addon used the Kitty base-layout key for every chord that had no direct binding. On Dvorak, Ctrl+J then ran actrl+cbinding, Ctrl+E ranctrl+d, and Ctrl+; ranctrl+z. The addon now uses the same rule as Core's key bindings. It falls back only when the typed key is one non-ASCII character, or when the base-layout key is a digit. The digit case is for AZERTY, where the digit keys type& " ' ( - _and Ctrl+& must still matchctrl+1. Some punctuation on non-Latin layouts still does not fall back, for example Hebrew'on the W key.In
@opentui/react, eachroot.render()call created a new reconciler container. It dropped the old tree and did not run its effect cleanups. A later call now updates the mounted tree, asreact-domdoes:const root = createRoot(renderer) root.render(<App theme="dark" />) root.render(<App theme="light" />) // App keeps its state
An explicit
root.render()also resets the internal error boundary. If a tree failed with an uncaught error, the next call tries it again.OpenCode draws each diff hunk as a separate
DiffRenderable. To line up the gutters, it put an invisible sign on line-1and measured the gutters again after mount. The newgutteroption sets that layout directly:const gutter = { paddingLeft: 2, minDigits: 4 } const first = new DiffRenderable(renderer, { diff: firstHunk, gutter }) const second = new DiffRenderable(renderer, { diff: secondHunk, gutter })minDigitsalways reserves the+/-sign column. Thus both code columns start at the same cell, also when a hunk has no signs. Line-1still works, for compatibility.Breaking changes
- breaking
core: A Box with a default-ignorable character in
customBorderChars, such as U+200E or U+FFA0, throwsInvalidArgumentat creation, anddrawGridrejects such a character. Use a border character that takes one cell. See Box.
Added
core: The
gutteroption and property ofDiffRenderablesetpaddingLeft,paddingRight, andminDigitsfor the line-number gutter. Diffs with the sameminDigitsalign their code columns. See Diff.core:
LineNumberRenderable.getGutterLayout()andsetGutterLayout()read and set the gutter padding, minimum digits, and minimum widths as aGutterLayout. See Line number gutter.
Changed
react: A later
root.render()updates the mounted tree, keeps component state, and tries again after a tree that failed with an uncaught error. Effects clean up when the tree is replaced, onroot.unmount(), and onrenderer.destroy(). See React bindings.keymap:
registerBaseLayoutFallback()uses the base-layout key only when the typed key is one non-ASCII character or the base-layout key is a digit. Dvorak Ctrl+J now matchesctrl+j, notctrl+c. See Built-in keymap addons.
Fixed
core: Bidirectional marks and other default-ignorable code points take no cell in layout, cursor movement, drawing, and
encodeUnicode. Before, a partial update of a row with such a mark wrote one column to the right. See Text and terminal cells.core: In a Textarea or Input, Right, Shift+Right, and Delete move over zero-width characters at the start of a line or a pasted chunk. A double or triple click at such a line start selects the same text as on a line without the prefix. See Textarea.
core: Delete, Backspace, Delete Line, and a selection delete remove text that holds only zero-width characters, such as U+200B, U+200D, or a combining mark. Delete Line also keeps a zero-width character that starts the next line. See Textarea.
core: With word wrap, a combining mark at the start of a typed or appended chunk stays on the line of its base character. Before, the mark moved to the next line and did not draw. See Text and terminal cells.
core: After a measure provider fails, it gets one more call before the next frame. Before, the node stayed 0 cells wide until
invalidateIntrinsicSize(). See Custom renderables.core: A left-button drag goes to the renderable that got the press, also when the first motion report is over a different renderable. A release over the drag source or its descendant no longer sends
dropand a secondup. See Interaction, focus, and selection.core: Key bindings fall back to the base-layout key when it is a digit, so AZERTY Ctrl+& matches
ctrl+1. They no longer fall back to a space base-layout key. See Keyboard input.keymap:
registerBaseLayoutFallback()no longer throws or reports anevent-match-resolver-errorwhen the base-layout key is a space. See Built-in keymap addons.core: On Node, a
-0integer argument goes to native code as 0. Before, calls such asEditBuffer.setCursor,pushScissorRect, andCliRenderer.hitTestthrewTypeError: Argument N must be an int32. See Runtime and platform support.
API changes and documentation: opentui.com/docs/releases/0.6.7
What's Changed
core: add diff gutter padding and digit options
native: count text work in tests and replace the wrap timing test
core: pass -0 as 0 to i32 and u32 arguments on Node
core: specify F3 and cursor-report readings in the parser vectors
native: keep a mark that starts a chunk with its glyph in word wrap
native: measure a failed custom measurement again on the next frame, once
core: capture a left drag to the renderable that got the press
keymap: fall back to the base-layout key only where the typed key cannot match
react: reuse root container across renders
native: share one text corpus between the Zig and TypeScript tests
native: step right over zero-width clusters at a line or chunk start
native: delete zero-width text on a line without cells
native: drop the corpus bug entries the zero-width delete fix resolves
native: give default-ignorable code points no cell
0.6.6
14 Oct 2026core: A custom renderable can override
protected defaultDimension(kind)to set thewidthorheightthat a nullish write restores. See Custom renderables.core: Assigning
nullorundefinedto a layout property restores the default for an omitted option, and removing a React or Solid style prop does the same. See Layout.core: Assigning
nullorundefinedtovisibleshows the node. Before,nullhid the node, so assignfalseto hide it.
Previously, most layout setters ignored
null,undefined, or both, so the node kept its old style. As a result, a React or Solid component that removed a style prop kept the old layout. A plugin that readUnit.Undefinedfrom a getter also could not write that value back. In 0.6.6, a nullish write restores the default that the constructor uses when the option is omitted:const box = new BoxRenderable(renderer, { paddingX: 2 }) box.paddingLeft = 5 // pads the left side by 5 box.paddingLeft = undefined // pads it by 2 again, from paddingXMargin, padding, gap, min and max sizes, and
flexBasisbecome unset, so an edge falls back to its shorthand again.top,right,bottom, andleftalso unset the native edge. Before, the getter reportedundefinedwhile layout kept the old offset.widthandheightbecome"auto",positionbecomes"relative",overflowbecomes"visible", andvisiblebecomestrue. Invalid values are still ignored. Not every default comes back. Class defaults such as the row direction ofScrollBoxRenderablestay as they are, andflexShrinkbecomes1.If a custom renderable passes its own
widthorheighttosuper(), it should override the newprotected defaultDimension(kind)method. A nullish write then restores that size instead of"auto". Built-in classes that size themselves, such asInputRenderable,TabSelectRenderable, andASCIIFontRenderable, already override it.Added
core: A custom renderable can override
protected defaultDimension(kind)to set thewidthorheightthat a nullish write restores. See Custom renderables.
Changed
core: Assigning
nullorundefinedto a layout property restores the default for an omitted option, and removing a React or Solid style prop does the same. See Layout.core: Assigning
nullorundefinedtovisibleshows the node. Before,nullhid the node, so assignfalseto hide it.
Fixed
core:
fillRect,drawBox,setCell,drawChar, and other calls with u32 fields now say "must be a non-negative integer" for NaN, Infinity, fractional, and negative values. Before, the error said that the value exceeded the u32 limit. See Buffer API.core: A box
titleorbottomTitleover 64 KiB throws the sameRangeErrorasdrawTextinstead of aNativeErrorwith theInvalidArgumentstatus. See Box.core: Nesting deeper than 256 levels, counting the root, throws a
NativeErrorwith the message "Scene nesting depth exceeds the native limit". Before, the message gave only theObjectLimitstatus. See Layout.
API changes and documentation: opentui.com/docs/releases/0.6.6
What's Changed
core: name the real cause in three boundary errors (u32 fields, box titles, nesting depth)
core: restore layout defaults on nullish writes
0.6.5
14 Oct 2026core: Renderables have the remaining Yoga
Nodestyle getters, such asgetHeight(),getMaxWidth(),getPadding(edge),getGap(gutter),getFlexDirection(), andgetAlignSelf(). Use them instead of style reads throughgetLayoutNode(). See Layout.core: read declared layout styles on renderables
OpenTUI 0.6 removed
Renderable.getLayoutNode(). Before 0.6, plugins used it to read the layout styles of host renderables that they did not create. Some plugins saved a container'sgetMaxWidth()andgetAlignSelf()so they could restore the container after they restyled it. Others checked the host layout withgetFlexDirection(), or sized a panel inside a padded sidebar withgetPadding(). 0.6 kept onlygetWidth()andgetMargin(edge), so this code had nothing to call.In 0.6.5, renderables have the other style getters of a Yoga
Node, with the same names and return types. To move, remove thegetLayoutNode()call:// Before 0.6 const saved = host.getLayoutNode().getMaxWidth() // 0.6.5 const saved = host.getMaxWidth()
These getters return the declared style, not the computed layout. A write shows up at once, before the next layout pass. After
box.maxWidth = 40,box.getMaxWidth()returns{ unit: Unit.Point, value: 40 }, butbox.widthstill holds the computed width from the last pass. Afterdestroy(), the getters do not throw. They return Yoga's unset values, asgetWidth()does.An edge getter reads only the value set for that exact edge, so this case needs care. The
paddingoption setsEdge.All,paddingXsetsEdge.Horizontal, andpaddingLeftsetsEdge.Left. Afterpadding: 2,getPadding(Edge.Left)returns aUnit.Undefinedvalue, but layout still pads the left side by 2. That is because Yoga falls back fromEdge.LefttoEdge.Horizontaland then toEdge.All. To get the declared left padding, check the edges in that same order:import { Edge, Unit } from "@opentui/core/yoga" const left = [Edge.Left, Edge.Horizontal, Edge.All] .map((edge) => sidebar.getPadding(edge)) .find((value) => value.unit !== Unit.Undefined)Renderables still have no getter for computed padding, margin, or border. For padding declared in points, the declared value and the computed value are the same.
Added
core: Renderables have the remaining Yoga
Nodestyle getters, such asgetHeight(),getMaxWidth(),getPadding(edge),getGap(gutter),getFlexDirection(), andgetAlignSelf(). Use them instead of style reads throughgetLayoutNode(). See Layout.
API changes and documentation: opentui.com/docs/releases/0.6.5
What's Changed
core: read declared layout styles on renderables
0.6.4
13 Oct 2026core: Kitty images in iTerm2 no longer go black or blank after a move, a scroll, or a layout change. The renderer now deletes each old placement by its placement id, because iTerm2 also frees the image data when it gets an image-wide delete.
native: delete Kitty placements by id so iTerm2 keeps the image
Fixed
core: Kitty images in iTerm2 no longer go black or blank after a move, a scroll, or a layout change. The renderer now deletes each old placement by its placement id, because iTerm2 also frees the image data when it gets an image-wide delete.
API changes and documentation: opentui.com/docs/releases/0.6.4
What's Changed
native: delete Kitty placements by id so iTerm2 keeps the image
Dependency tracking · free for 3 projects
Know when your own dependencies move
@opentui/react published 131 releases in the last 12 months. Inlinr folds the routine ones into a line and keeps the few that need you.
Advisories reach you
An email when a critical or high advisory affects a project, before the next install.
Upgrades checked first
Peer ranges, Node, ESM-only and types changes read before you bump.
Only what matters
Majors and breaking notes stay; patches and sibling bumps fold into one line.
Registry facts last checked 25 Oct 2026. Releases and advisories last read 25 Oct 2026.