The imperative API gives you direct control over widget creation, layout, and the event loop. This approach is ideal when you need fine-grained control over your UI or prefer a procedural programming style.
Initializing the Application
Start by initializing the Kraken TUI system and creating widgets:
import { Kraken , Box , Text , Input , Select } from "kraken-tui" ;
const app = Kraken . init ();
Kraken.init() enters alternate screen mode, raw mode, and enables mouse capture. Always call app.shutdown() to restore terminal state.
Create widgets using class constructors with configuration objects:
Box Containers
const root = new Box ({
width: "100%" ,
height: "100%" ,
flexDirection: "column" ,
padding: 1 ,
gap: 1 ,
});
// Set as application root
app . setRoot ( root );
Text Display
const header = new Text ({
content: "# Kraken TUI Demo \n\n **Interactive dashboard**" ,
format: "markdown" ,
fg: "cyan" ,
});
header . setWidth ( "100%" );
header . setHeight ( 4 );
Text widgets support three formats: "plain", "markdown", and "code". Set the language property for syntax highlighting.
const input = new Input ({
width: 30 ,
height: 3 ,
border: "rounded" ,
fg: "white" ,
maxLength: 40 ,
});
input . setFocusable ( true );
const select = new Select ({
options: [ "Dark Mode" , "Light Mode" , "Solarized" , "Nord" , "Dracula" ],
width: 25 ,
height: 7 ,
border: "rounded" ,
fg: "white" ,
});
select . setFocusable ( true );
const scrollBox = new ScrollBox ({
width: "100%" ,
height: 12 ,
border: "single" ,
fg: "white" ,
});
const scrollContent = new Text ({
content: "Long scrollable text content..." ,
format: "plain" ,
fg: "white" ,
});
scrollContent . setWidth ( "100%" );
scrollContent . setHeight ( 40 );
scrollBox . append ( scrollContent );
Building Layouts
Compose widgets into hierarchies using append():
// Create a row layout
const middleRow = new Box ({
width: "100%" ,
flexDirection: "row" ,
gap: 2 ,
});
middleRow . append ( inputLabel );
middleRow . append ( input );
middleRow . append ( selectLabel );
middleRow . append ( select );
// Add to root
root . append ( header );
root . append ( middleRow );
root . append ( scrollBox );
root . append ( statusBar );
Layout Properties
Control widget sizing and positioning:
Dimensions : width, height (numbers, “100%”, or “auto”)
Spacing : padding, margin, gap
Flex : flexDirection (“row” | “column”), justifyContent, alignItems
const container = new Box ({
width: "100%" ,
flexDirection: "row" ,
justifyContent: "space-between" ,
alignItems: "center" ,
padding: [ 2 , 4 , 2 , 4 ], // [top, right, bottom, left]
gap: 2 ,
});
Managing Focus
Control which widget receives keyboard input:
// Make widget focusable
input . setFocusable ( true );
// Set initial focus
input . focus ();
// Programmatic focus navigation
app . focusNext ();
app . focusPrev ();
// Get currently focused widget
const focused = app . getFocused ();
Register widgets with developer-assigned IDs:
app . setId ( "input" , input );
app . setId ( "select" , select );
// Retrieve by ID later
const handle = app . getHandle ( "input" );
Apply colors, borders, and text styles:
// Colors
widget . setForeground ( "#89b4fa" );
widget . setBackground ( "#1e1e2e" );
// Text styles
text . setBold ( true );
text . setItalic ( true );
text . setUnderline ( true );
// Borders
box . setBorder ( "rounded" ); // "none" | "single" | "double" | "rounded" | "bold"
// Visibility
widget . setVisible ( false );
Color values support hex codes ("#RRGGBB"), named colors ("red"), and 256-color palette indices (196).
Event Loop Patterns
The imperative API gives you full control over the event loop.
Basic Loop (~60fps)
let running = true ;
while ( running ) {
// Read input with 16ms timeout (~60fps)
app . readInput ( 16 );
// Drain all buffered events
const events = app . drainEvents ();
for ( const event of events ) {
if ( event . type === "key" && event . keyCode === KeyCode . Escape ) {
running = false ;
break ;
}
}
if ( ! running ) break ;
// Render the frame
app . render ();
}
app . shutdown ();
Handling Different Event Types
for ( const event of events ) {
switch ( event . type ) {
case "key" :
if ( event . keyCode === KeyCode . Escape ) {
running = false ;
}
break ;
case "submit" :
if ( event . target === input . handle ) {
const value = input . getValue ();
statusBar . setContent ( `Submitted: " ${ value } "` );
} else if ( event . target === select . handle ) {
const idx = select . getSelected ();
const option = select . getOption ( idx );
console . log ( "Selected:" , option );
}
break ;
case "change" :
if ( event . target === select . handle && event . selectedIndex != null ) {
const option = select . getOption ( event . selectedIndex );
applyTheme ( option );
}
break ;
case "mouse" :
// Mouse events include x, y coordinates and button state
break ;
case "focus" :
// Focus changed to event.target
break ;
}
}
Animation-Aware Loop
Optimize rendering when animations are active:
import { PERF_ACTIVE_ANIMATIONS } from "kraken-tui/loop" ;
while ( running ) {
const animating = app . getPerfCounter ( PERF_ACTIVE_ANIMATIONS ) > 0 n ;
if ( animating ) {
// Non-blocking, render at 60fps
app . readInput ( 0 );
await Bun . sleep ( 16 );
} else {
// Block on input when idle to save CPU
app . readInput ( 100 );
}
for ( const event of app . drainEvents ()) {
// Handle events...
}
app . render ();
}
Modify widget properties at runtime:
// Update text content
text . setContent ( "New content" );
// Change colors dynamically
box . setBackground ( "#2e3440" );
text . setForeground ( "#d8dee9" );
// Modify layout
box . setWidth ( 50 );
box . setHeight ( "auto" );
// Update input state
input . setValue ( "preset text" );
input . setMaxLength ( 100 );
// Update select options
select . clearOptions ();
select . addOption ( "New Option 1" );
select . addOption ( "New Option 2" );
select . setSelected ( 0 );
Complete Example
Here’s a minimal interactive application:
import { Kraken , Box , Text , Input , KeyCode } from "kraken-tui" ;
import type { KrakenEvent } from "kraken-tui" ;
const app = Kraken . init ();
// Build UI
const root = new Box ({
width: "100%" ,
height: "100%" ,
flexDirection: "column" ,
padding: 1 ,
gap: 1 ,
});
const header = new Text ({
content: "# Enter Your Name" ,
format: "markdown" ,
fg: "cyan" ,
});
header . setWidth ( "100%" );
header . setHeight ( 3 );
const input = new Input ({
width: 40 ,
height: 3 ,
border: "rounded" ,
fg: "white" ,
maxLength: 50 ,
});
input . setFocusable ( true );
const status = new Text ({
content: "Press Enter to submit, Esc to quit" ,
fg: "bright-black" ,
});
status . setWidth ( "100%" );
status . setHeight ( 1 );
root . append ( header );
root . append ( input );
root . append ( status );
app . setRoot ( root );
input . focus ();
// Event loop
let running = true ;
while ( running ) {
app . readInput ( 16 );
const events = app . drainEvents ();
for ( const event of events ) {
if ( event . type === "key" && event . keyCode === KeyCode . Escape ) {
running = false ;
break ;
}
if ( event . type === "submit" && event . target === input . handle ) {
const value = input . getValue ();
status . setContent ( `Hello, ${ value } !` );
}
}
if ( ! running ) break ;
app . render ();
}
app . shutdown ();
Best Practices
Call Kraken.init() once at application startup. Store the instance for the entire lifecycle.
Always call app.setRoot() before starting the event loop.
Always call app.shutdown() before exit to restore terminal state. Use try-finally if needed.
Call app.drainEvents() in every frame to process all buffered input.
Call app.render() at the end of each loop iteration to update the terminal.
Next Steps
JSX API Learn about the declarative JSX syntax and signals
Event Handling Master keyboard, mouse, and focus events
Animations Add smooth transitions and choreography
Themes Apply and customize visual themes