Troubleshooting
Common Issues
Blank screen / no output
- Check that
using term = createTerm()is called beforerender(). - Ensure stdout is a TTY. Piped output needs
renderString()instead ofrender(). - Verify the component returns visible content (not
nullor empty fragments).
Content overflows / truncation
- Text auto-truncates by default. Use
wrap="wrap"on Text to wrap instead, orwrap="overflow"to allow overflow. - Check
overflow="hidden"on the parent Box to clip children. - For scrollable content, use
overflow="scroll"with ascrollToprop.
Incremental rendering mismatches
- Run with
SILVERY_STRICT=1to compare incremental vs fresh render output on every frame. - If a mismatch is found, file a bug with the minimal reproduction.
- Use
withDiagnostics({ checkIncremental: true })in tests to catch these automatically.
useInput not receiving keys
- Ensure
InputLayerProviderwraps your app if usinguseInputLayer. - Check that stdin is in raw mode (
term.hasInput()returnstrue). - Verify no other input layer is consuming the key before yours (layers are LIFO).
Layout oscillation / infinite loops
- Silvery has built-in containment for
useContentRectthat detects and breaks oscillation loops. - If you see oscillation, check for circular dependencies in layout — e.g., a component that changes its size based on
useContentRectin a way that triggers another layout. - Avoid setting
widthorheightdynamically based onuseContentRectof the same Box.
Flickering in tmux / Zellij
- Silvery uses Synchronized Update Mode (DEC 2026) by default, which prevents flicker. Verify your multiplexer version supports it (tmux 3.2+).
- To disable sync updates for debugging:
SILVERY_SYNC_UPDATE=0.
Colors not appearing
- Check
term.hasColor()— returnsnullifNO_COLORis set orTERM=dumb. - Verify
COLORTERM=truecoloris set for true color support. - Some CI environments strip ANSI codes. Use
renderString(<App />, { plain: true })for those.
Kitty keyboard shortcuts not working
run()auto-enables the Kitty protocol on supported terminals. If usingcreateApp().run(), passkitty: trueexplicitly.- Check that your terminal supports it (Ghostty, Kitty, WezTerm, foot — see Terminal Capabilities).
- iTerm2 and Terminal.app do not support the Kitty protocol.
Flexily vs Yoga layout differences
If you migrated from Ink (which uses Yoga), some layout behaviors differ with Flexily:
- Percentage widths: Flexily resolves
width="50%"against the parent's content area. Yoga resolves against the parent's total width including padding. If your layout is off by a few cells, check padding on the parent. - Default
flexShrink: Both default to 1, but Flexily may clamp earlier on zero-width children. If a child collapses unexpectedly, setflexShrink={0}explicitly. flexBasis="auto": Yoga uses the intrinsic content size. Flexily does the same but measures text differently for multi-line content. If text wraps unexpectedly, set an explicitwidth.- Gap: Flexily supports
gap,rowGap,columnGapthe same as Yoga. If gaps don't appear, verifyflexDirectionis set (gap only applies between flex children along the main axis).
Switch engines to isolate: SILVERY_ENGINE=yoga bun run app.ts
Focus routing debugging
If focus isn't moving where expected:
- Enable inspector:
SILVERY_DEV=1to visualize the focus tree and see which nodes arefocusable. - Check
focusableprop: Only<Box focusable>nodes participate in focus. Text nodes cannot receive focus. - Verify
testID: Spatial navigation overrides (nextFocusUp,nextFocusDown, etc.) reference nodes bytestID. Missing or mismatched IDs are silently ignored. - Scope boundaries: If Tab doesn't leave a subtree, a parent has
focusScopewhich restricts cycling to that subtree. Remove or restructure the scope. - Debug with
FocusManager: Accessfm.activeIdandfm.focusOriginto see what's focused and why.
VirtualList blank rendering
VirtualList shows blank rows when:
itemHeightis wrong: VirtualList requires a fixeditemHeight. If the actual rendered height differs, items misalign and blank gaps appear. Measure a single item first.dataarray changes identity on every render: Wrap withuseMemoor hoist the array. Identity changes cause VirtualList to recalculate offsets.- Container has no height: VirtualList needs a parent with a known height (explicit
heightprop or flex layout). Without it, the visible window is zero and nothing renders. scrollTois out of range: AscrollTovalue past the last item shows blank space. Clamp toMath.max(0, data.length - visibleCount).
useContentRect returning zeros
useContentRect() returns { width: 0, height: 0 } on the first render because layout hasn't run yet. This is by design.
- Normal case: The component re-renders once layout completes (usually the same frame). Your UI should handle
width === 0gracefully (returnnullor a placeholder). - Not updating at all: Ensure the component's parent has a concrete size. A chain of
flexGrow={1}without a root-level size means layout can't resolve. - In tests: Call
app.debug()after render — if the layout is correct in the debug output, the hook is working. Ifwidthstays 0, check thatcreateRendererhascolsandrowsset. - Oscillation: If the component changes its own size based on
useContentRect, it can loop. Silvery's built-in containment detects this and breaks the cycle automatically.
Debugging
Runtime Debug
bash
# Enable incremental vs fresh render comparison
SILVERY_STRICT=1 bun run app.ts
# Write debug output to file
DEBUG=silvery:* DEBUG_LOG=/tmp/silvery.log bun run app.ts
tail -f /tmp/silvery.logTest Debug
tsx
const app = render(<MyComponent />)
app.debug() // Print current frame to console
console.log(app.ansi) // Print with colors
console.log(app.text) // Print plain text (no ANSI)