Skip to content

ByteLandTechnology/tinky

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

10 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Tinky

React for CLIs, re-imagined with the Taffy layout engine

ไธญๆ–‡ ยท ๆ—ฅๆœฌ่ชž


License: MIT npm version

Tinky is a modern React-based framework for building beautiful and interactive command-line interfaces. It leverages the powerful Taffy layout engine to provide CSS Flexbox and Grid layout support in the terminal.

โœจ Features

  • ๐ŸŽจ React Components โ€” Build CLIs using familiar React patterns and JSX syntax
  • ๐Ÿ“ Flexbox & Grid Layout โ€” Full CSS Flexbox and CSS Grid support powered by Taffy
  • โŒจ๏ธ Keyboard Input โ€” Built-in hooks for handling keyboard input and focus management
  • ๐ŸŽฏ Focus Management โ€” Tab/Shift+Tab navigation with customizable focus behavior
  • ๐Ÿ–ผ๏ธ Borders & Backgrounds โ€” Rich styling with borders, background colors, and more
  • โ™ฟ Accessibility โ€” Screen reader support with ARIA attributes
  • ๐Ÿ”„ Hot Reloading โ€” Fast development with React DevTools support
  • ๐Ÿ“ฆ TypeScript First โ€” Full TypeScript support with comprehensive type definitions

๐Ÿ“ฆ Installation

# Using npm
npm install tinky react

# Using yarn
yarn add tinky react

# Using pnpm
pnpm add tinky react

# Using bun
bun add tinky react

๐Ÿš€ Quick Start

import { render, Box, Text } from "tinky";

function App() {
  return (
    <Box flexDirection="column" padding={1}>
      <Text color="green" bold>
        Hello, Tinky! ๐ŸŽ‰
      </Text>
      <Text>Build beautiful CLIs with React</Text>
    </Box>
  );
}

render(<App />);

๐Ÿ“š Components

Box

The <Box> component is a fundamental building block. It's like a <div> in the browser, supporting Flexbox and Grid layouts.

import { Box, Text } from "tinky";

// Flexbox layout
<Box flexDirection="row" gap={2}>
  <Text>Left</Text>
  <Text>Right</Text>
</Box>

// Grid layout
<Box display="grid" gridTemplateColumns="1fr 2fr 1fr" gap={1}>
  <Text>Col 1</Text>
  <Text>Col 2</Text>
  <Text>Col 3</Text>
</Box>

// With borders and padding
<Box borderStyle="round" borderColor="cyan" padding={1}>
  <Text>Styled Box</Text>
</Box>

Text

The <Text> component renders styled text with colors, bold, italic, and more.

import { Text } from "tinky";

<Text color="blue">Blue text</Text>
<Text backgroundColor="red" color="white">Highlighted</Text>
<Text bold italic underline>Styled text</Text>
<Text color="#ff6600">Hex colors work too!</Text>

Static

The <Static> component renders static content that won't be updated. Perfect for logs and history.

import { Static, Text } from "tinky";

const logs = ["Log 1", "Log 2", "Log 3"];

<Static items={logs}>{(log, index) => <Text key={index}>{log}</Text>}</Static>;

Transform

The <Transform> component allows you to transform the output of its children.

import { Transform, Text } from "tinky";

<Transform transform={(output) => output.toUpperCase()}>
  <Text>hello</Text>
</Transform>;
// Renders: HELLO

Newline & Spacer

import { Box, Text, Newline, Spacer } from "tinky";

// Newline - adds vertical space
<Box flexDirection="column">
  <Text>Line 1</Text>
  <Newline count={2} />
  <Text>Line 2</Text>
</Box>

// Spacer - flexible space in flex containers
<Box>
  <Text>Left</Text>
  <Spacer />
  <Text>Right</Text>
</Box>

๐Ÿช Hooks

useInput

Handle keyboard input in your components.

import { useInput, useApp } from "tinky";

function MyComponent() {
  const { exit } = useApp();

  useInput((input, key) => {
    if (key.escape) {
      exit();
    }
    if (key.upArrow) {
      // Handle up arrow
    }
    if (input === "q") {
      exit();
    }
  });

  return <Text>Press 'q' to quit</Text>;
}

useApp

Access the app instance to control exit behavior.

import { useApp } from "tinky";

function MyComponent() {
  const { exit } = useApp();

  // Exit with error
  exit(new Error("Something went wrong"));

  // Exit normally
  exit();
}

useFocus & useFocusManager

Manage focus for interactive components.

import { useFocus, Box, Text } from "tinky";

function FocusableItem({ label }: { label: string }) {
  const { isFocused } = useFocus();

  return (
    <Box borderStyle={isFocused ? "bold" : "single"}>
      <Text color={isFocused ? "green" : "white"}>{label}</Text>
    </Box>
  );
}

useStdin, useStdout, useStderr

Direct access to stdin, stdout, and stderr streams.

import { useStdout, useEffect } from "tinky";

function MyComponent() {
  const { write } = useStdout();

  useEffect(() => {
    write("Hello from stdout!\n");
  }, []);

  return null;
}

๐ŸŽจ Styling

Flexbox Properties

<Box
  flexDirection="row" // row, row-reverse, column, column-reverse
  justifyContent="center" // flex-start, flex-end, center, space-between, space-around
  alignItems="center" // flex-start, flex-end, center, stretch
  flexWrap="wrap" // nowrap, wrap, wrap-reverse
  flexGrow={1}
  flexShrink={0}
  gap={2}
/>

Grid Properties

<Box
  display="grid"
  gridTemplateColumns="1fr 2fr 1fr"
  gridTemplateRows="auto 1fr"
  columnGap={1}
  rowGap={1}
  justifyItems="center"
  alignItems="center"
/>

Border Styles

<Box borderStyle="single" />   // โ”Œโ”€โ”
<Box borderStyle="double" />   // โ•”โ•โ•—
<Box borderStyle="round" />    // โ•ญโ”€โ•ฎ
<Box borderStyle="bold" />     // โ”โ”โ”“
<Box borderStyle="classic" />  // +--+

Colors

Tinky supports multiple color formats:

<Text color="red" />                    // Named colors
<Text color="#ff6600" />                // Hex colors
<Text color="rgb(255, 102, 0)" />       // RGB colors
<Text color="ansi256:208" />            // ANSI 256 colors

๐Ÿ”ง API Reference

For complete API documentation, see the API Docs.

render(element, options?)

Render a React element to the terminal.

import { render } from "tinky";

const { unmount, waitUntilExit, rerender, clear } = render(<App />, {
  stdout: process.stdout,
  stdin: process.stdin,
  stderr: process.stderr,
  exitOnCtrlC: true,
  patchConsole: true,
});

// Wait for the app to exit
await waitUntilExit();

// Rerender with new props
rerender(<App newProp={true} />);

// Unmount the app
unmount();

// Clear the output
clear();

measureElement(ref)

Measure the dimensions of a rendered element.

import { measureElement, Box, useRef, useEffect } from "tinky";

function MyComponent() {
  const ref = useRef(null);

  useEffect(() => {
    if (ref.current) {
      const { width, height } = measureElement(ref.current);
      console.log(`Size: ${width}x${height}`);
    }
  }, []);

  return <Box ref={ref}>Content</Box>;
}

๐Ÿงช Testing

Tinky uses Bun for testing. Run the test suite:

bun test

๐Ÿ“„ License

MIT ยฉ ByteLandTechnology

๐Ÿ™ Acknowledgements

  • Ink โ€” The original React for CLIs
  • Taffy โ€” High-performance CSS layout engine
  • React โ€” The UI library that makes this possible