rescript-opentui-react
v0.1.0
Published
ReScript bindings for OpenTUI React - Build terminal user interfaces with React and type-safe functional programming
Maintainers
Readme
ReScript OpenTUI React Bindings
ReScript bindings for OpenTUI React - Build beautiful terminal user interfaces with React and ReScript.
Features
✅ Full Type Safety - Leverages ReScript's powerful type system
✅ React Components - All OpenTUI React components with type-safe props
✅ Hooks Support - Complete bindings for all OpenTUI React hooks
✅ Functional Style - Natural functional programming patterns
✅ Pattern Matching - Use ReScript's pattern matching for event handling
✅ Styled Text - Full support for styled text with proper types
Installation
# Install the package
npm install rescript-opentui-react
# Or with yarn/pnpm/bun
yarn add rescript-opentui-react
pnpm add rescript-opentui-react
bun add rescript-opentui-react
# You also need the peer dependencies
npm install @opentui/core @opentui/react react rescriptQuick Start
// App.res
open OpenTUI
@react.component
let make = () => {
<Box style={padding: 2, flexDirection: #column}>
<Text
content="Hello from ReScript!"
style={fg: Core.Colors.brightCyan}
/>
</Box>
}
// Start the app
render(<App />)->ignoreComponents
Box
Container component with flexible layout options:
<Box
title="My Box"
style={
border: true,
borderStyle: #double,
padding: 2,
flexDirection: #column,
}>
{children}
</Box>Text
Display styled text:
<Text
content="Hello World"
style={
fg: Core.Colors.green,
attributes: Core.TextAttributes.bold,
}
/>Input
Text input field with event handlers:
<Input
placeholder="Enter text..."
value=inputValue
focused=isFocused
onInput={value => setInputValue(_ => value)}
onSubmit={value => handleSubmit(value)}
/>Select
Dropdown selection component:
let options = [
{label: "Option 1", value: "1"},
{label: "Option 2", value: "2"},
]
<Select
options
selectedIndex=0
focused=isFocused
onChange={(index, option) => handleChange(index, option)}
/>ScrollBox
Scrollable container:
<ScrollBox
focused=isFocused
style={height: 10, overflow: #scroll}>
{/* Long content */}
</ScrollBox>ASCIIFont
Large ASCII text rendering:
<ASCIIFont
text="TITLE"
font="Standard"
style={fg: Core.Colors.brightYellow}
/>Hooks
useKeyboard
Handle keyboard events:
Hooks.useKeyboard(key => {
switch key.name {
| Some("up") => moveUp()
| Some("down") => moveDown()
| Some("return") => submit()
| Some("c") when key.ctrl => exit()
| _ => ()
}
})useRenderer
Access the renderer instance:
let renderer = Hooks.useRenderer()
switch renderer {
| Some(r) => // Use renderer
| None => ()
}useTerminalDimensions
Get terminal size:
let (width, height) = Hooks.useTerminalDimensions()useResize
Handle terminal resize:
Hooks.useResize((width, height) => {
Js.log2("Terminal resized to:", (width, height))
})Custom Hooks
The package includes helpful custom hooks:
// Focus management
let (focusedIndex, isFocused, getFocusedItem) =
Hooks.useFocusManager(["input1", "input2", "button"])
// Input state management
let (value, handleInput, handleSubmit, clear) =
Hooks.useInput(~initialValue="", ~onSubmit=submitForm)
// Select state management
let (selectedIndex, handleChange, getSelectedOption) =
Hooks.useSelect(~options, ~defaultIndex=0)Styling
Colors
Use predefined colors:
open OpenTUI.Core.Colors
let styles = {
fg: brightCyan,
bg: black,
borderColor: green,
}Text Attributes
Combine text attributes:
open OpenTUI.Core
let style = {
attributes: combineAttributes([
TextAttributes.bold,
TextAttributes.italic,
TextAttributes.underline,
])
}Styled Text Functions
Create styled text using the formatting functions:
open OpenTUI.Core
// Simple bold text
<Text content={toStyledText(bold("Bold text"))} />
// Chaining styles
<Text content={
toStyledText(
fgChunk(Colors.yellow)(italicChunk(bold("Bold italic yellow")))
)
} />
// With background color
<Text content={
toStyledText(
bgChunk(Colors.blue)(fgChunk(Colors.white)(bold("White on blue")))
)
} />
// Or use style attributes for simpler cases
<Text
content="Styled text"
style={
fg: Colors.cyan,
attributes: combineAttributes([
TextAttributes.bold,
TextAttributes.italic,
]),
}
/>Style Record
Complete style options:
let myStyle: OpenTUI.style = {
// Colors
fg: "#00ff00",
bg: "black",
// Layout
width: 40,
height: 10,
padding: 2,
margin: 1,
// Border
border: true,
borderStyle: #double,
borderColor: "blue",
// Flexbox
flexDirection: #row,
justifyContent: #center,
alignItems: #center,
gap: 2,
// Position
position: #absolute,
top: 5,
left: 10,
}Examples
Counter App
// examples/Counter.res
@react.component
let make = () => {
let (count, setCount) = React.useState(() => 0)
Hooks.useKeyboard(key => {
switch key.name {
| Some("up") => setCount(prev => prev + 1)
| Some("down") => setCount(prev => prev - 1)
| _ => ()
}
})
<Box>
<Text content={`Count: ${Int.toString(count)}`} />
</Box>
}Login Form
See examples/LoginForm.res for a complete login form implementation with:
- Multiple input fields
- Focus management
- Form validation
- Status messages
Running Examples
# Build the ReScript code
bun run build
# Run examples
bun run example:basic
bun run example:counter
bun run example:loginType Definitions
The bindings expose all necessary types:
type style = { /* all style properties */ }
type keyInfo = { name: option<string>, ctrl: bool, alt: bool, shift: bool }
type selectOption = { label: string, value: string, disabled?: bool }
type rendererDevelopment
# Install dependencies
bun install
# Build ReScript code
bun run build
# Watch mode for development
bun run watch
# Clean build artifacts
bun run cleanProject Structure
packages/rescript/
├── src/
│ ├── OpenTUI.res # Main module
│ ├── OpenTUI_Types.res # Type definitions
│ ├── OpenTUI_Core.res # Core utilities
│ ├── OpenTUI_Components.res # React components
│ └── OpenTUI_Hooks.res # React hooks
├── examples/
│ ├── Basic.res # Basic example
│ ├── Counter.res # Counter app
│ └── LoginForm.res # Login form
├── package.json
├── rescript.json
└── README.mdTips
- Pattern Matching: Use ReScript's pattern matching for cleaner event handling
- Variants: Use polymorphic variants for finite states (focus, status, etc.)
- Option Types: Safely handle nullable values with option types
- Records: Use record types for complex configuration objects
- Pipe Operator: Chain operations with the pipe operator (
->)
Known Issues
Bun Runtime Crashes
There are currently known Bun runtime bugs that cause segmentation faults with certain OpenTUI configurations:
flexDirection: #row- Causes Bun to crash. Use#columninstead.borderStyle: #bold- Causes Bun to crash. Use other border styles like#single,#double, or#round.
These issues appear to be bugs in Bun's FFI interaction with OpenTUI's native Zig components. See KNOWN_ISSUES.md for more details.
Select Component
The selectOption type uses name instead of label for the display text:
let options = [
{name: "Option 1", value: "1", description: "First option"},
{name: "Option 2", value: "2"},
]Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
License
MIT - See the main OpenTUI repository for details.
