A curses-like blazingly fast react renderer
See it in action:
Still here? Let's go deeper:
You can easily build full-scale terminal UI applications like:
Just run npm init react-curse
answer a few questions and you are ready to go
import React from 'react'
import ReactCurse, { Text } from 'react-curse'
const App = ({ text }) => {
return <Text color="Red">{text}</Text>
}
ReactCurse.render(<App text="hello world" />)
import React, { useState } from 'react'
import ReactCurse, { Text, useInput, exit } from 'react-curse'
const App = () => {
const [counter, setCounter] = useState(0)
useInput(
input => {
if (input === 'k') setCounter(counter + 1)
if (input === 'j') setCounter(counter - 1)
if (input === 'q') exit()
},
[counter]
)
return (
<Text>
counter: <Text bold>{counter}</Text>
</Text>
)
}
ReactCurse.render(<App />)
import React from 'react'
import ReactCurse, { useAnimation } from 'react-curse'
const App = () => {
const { interpolate, interpolateColor } = useAnimation(1000)
return <Text width={interpolate(0, 80)} background={interpolateColor('#282828', '#d79921')} />
}
ReactCurse.render(<App />)
<Text>
Base component\ The only component required to do anything\ Every other component uses this one to draw
number
| string
Position from top left corner relative to parent\
Content will be cropped by parent\
See absolute
to avoid this behavior\
Example: 32, '100%', '100%-8'
number
| string
Size of block, will be cropped by parent\
See absolute
to avoid this behavior
boolean
Makes position and size ignoring parent container
number
| string
Background and foreground color\
Example: 31, 'Red', '#f04020', '#f42'
boolean
Clears block before drawing content\
height
and width
boolean
Moves cursor to a new line after its content relative to parent
boolean
Text modifiers
<Text color="Red" block>hello world</Text>
<Text color="Green" bold block>hello world</Text>
<Text color="BrightBlue" underline block>hello world</Text>
<Text y={0} x="50%">
<Text color={128} italic block>hello world</Text>
<Text x="100%-11" color="#1ff" strikethrough block>hello world</Text>
<Text x="50%-5" color="#e94691" inverse>hello world</Text>
</Text>
<Input>
Text input component with cursor movement and text scroll support\ If its height is more than 1, then it switches to multiline, like textarea\ Most terminal shortcuts are supported
boolean
= true
Makes it active
'text'
| 'password'
| 'hidden'
= ‘text'
string
number
| string
() => void
(string) => void
(string) => void
<Input background="#404040" height={1} width={8} />
<Banner>
Displays big text
number
| string
number
| string
string
<Banner>{new Date().toTimeString().substring(0, 8)}</Banner>
<Bar>
Displays vertical or horizontal bar with 1/8 character resolution
'vertical'
| 'horizontal'
number
<>
{[...Array(24)].map((_, index) => (
<Bar key={index} type="vertical" x={index * 2} height={(index + 1) / 8} />
))}
</>
Compare to <Text>
<Block>
Aligns content
number
'left'
| 'center'
| 'right'
= 'left'
<Block>left</Block>
<Block align="center">center</Block>
<Block align="right">right</Block>
<Canvas>
Create a canvas for drawing with one these modes
{ h: 1, w: 1 }
| { h: 2, w: 1 }
| { h: 2, w: 2 }
| { h: 4, w: 2 }
Pixels per character
number
Size in pixels
Point
| Line
)[]
<Point>
Draws a point at the coordinates
number
number
| string
<Line>
Draws a line using coordinates
number
number
| string
<Canvas width={80} height={6}>
<Point x={1} y={1} color="Yellow" />
<Line x={0} y={5} dx={79} dy={0} />
</Canvas>
Braille's font demo ({ h: 4, w: 2 }
)
<Frame>
Draws frame around its content
string
'single'
| 'double'
| 'rounded'
= 'single'
number
<Frame type="single" color="Red">single border type</Frame>
<Frame type="double" color="Green" y={0}>double border type</Frame>
<Frame type="rounded" color="Blue" y={0}>rounded border type</Frame>
<List>
Creates a list with navigation support\ Vim shortcuts are supported
boolean
number
}any[]
(object) => JSX.Element
number
boolean
boolean
boolean
boolean
= true
any
(object) => void
(object) => void
const items = [...Array(8)].map((_, index) => ({ id: index + 1, title: `Task ${index + 1}` }))
return (
<List
data={items}
renderItem={({ item, selected }) => <Text color={selected ? 'Green' : undefined}>{item.title}</Text>}
/>
)
<ListTable>
: <List>
Creates a table with navigation support\ Vim shortcuts are supported
'cell'
| 'row'
= 'cell'
any[]
(object) => JSX.Element
any[][]
const head = ['id', 'title']
const items = [...Array(8)].map((_, index) => [index + 1, `Task ${index + 1}`])
return (
<ListTable
head={head}
renderHead={({ item }) =>
item.map((i, key) => (
<Text key={key} width={8}>
{i}
</Text>
))
}
data={items}
renderItem={({ item, x, y, index }) =>
item.map((text, key) => (
<Text key={key} color={y === index && x === key ? 'Green' : undefined} width={8}>
{text}
</Text>
))
}
/>
)
<Scrollbar>
Draws a scrollbar with 1/8 character resolution
'vertical'
| 'horizontal'
= 'vertical'
number
number
number
number
| string
<Scrollbar type="horizontal" offset={10} limit={80} length={160} />
<Separator>
Draws a vertical or horizontal line
'vertical'
| 'horizontal'
number
<Separator type="vertical" height={3} />
<Separator type="horizontal" y={1} x={1} width={79} />
<Spinner>
Draws an animated spinner
string
<Spinner block />
<Spinner color="BrightGreen">-\|/</Spinner>
<View>
Creates a scrollable viewport\ Vim shortcuts are supported
boolean
number
boolean
boolean
= true
any
<View>{JSON.stringify(json, null, 2)}</View>
useAnimation
number
, fps?: 'number'
= 60
) => object
Creates a timer for a specified duration\ That gives you time and interpolation functions each frame of animation
number
number
, to: number
, delay?: number
)string
, to: string
: delay?: number
)const { ms } = useAnimation(1000, 4)
return ms // 0, 250, 500, 750, 1000
const { interpolate } = useAnimation(1000, 4)
return interpolate(0, 80) // 0, 20, 40, 60, 80
const { interpolateColor } = useAnimation(1000, 4)
return interpolateColor('#000', '#0f8') // #000, #042, #084, #0c6, #0f8
<Trail>
Mutate array of items to show one by one with latency
number
JSX.Element[]
const items = [...Array(8)].map((_, index) => ({ id: index + 1, title: `Task ${index + 1}` }))
return (
<Trail delay={100}>
{items.map(({ id, title }) => (
<Text key={id} block>
{title}
</Text>
))}
</Trail>
)
useTrail
number
, items: JSX.Element[]
, key?: string
= 'key'
) => JSX.Element[]
Same as <Trail>
but hook\
You can pass it to data
property of <List>
component for example
<List data={useTrail(items)} />
useChildrenSize
string
) => object
Gives you content size
number
useChildrenSize('1\n22\n333') // { height: 3, width: 3 }
useClipboard
array
Allows you to work with the system clipboard
() => string
(value: string) => void
const { getClipboard, setClipboard } = useClipboard()
const string = getClipboard()
setClipboard(string.toUpperCase()) // copied
useInput
(string) => void
, dependencies: any[]
) => void
Allows you to handle keyboard input
set[(counter, setCounter)] = useState(0)
useInput(
input => {
if (input === 'k') setCounter(counter + 1)
if (input === 'j') setCounter(counter - 1)
},
[counter]
)
useMouse
(object) => void
, dependencies: any[]
)Allows you to handle mouse input
set[(counter, setCounter)] = useState(0)
useMouse(
event => {
if (event.type === 'wheelup') setCounter(counter + 1)
if (event.type === 'wheeldown') setCounter(counter - 1)
},
[counter]
)
useSize
object
Gives you terminal size\ Updates when size is changing
number
useSize() // { height: 24, width: 80 }
useWordWrap
string
, width?: number
) => object
Gives your text a word wrap
number
useWordWrap('hello world', 5) // hello\nworld
render
(children: JSX.Element
) => void
Renders your fullscreen application to stdout
inline
(children: JSX.Element
) => void
Renders your inline application to stdout
bell
void
Makes a terminal bell
bell() // ding
exit
number
= 0
) => void
Allows you to exit from an application that waits for user input or has timers
useInput(input => {
if (input === 'q') exit()
})