A TypeScript/JSX library for building Tidbyt apps. Replaces Pixlet's Starlark syntax with familiar JSX components.
Warning: This project is experimental. The README and codebase are AI-generated. Expect breaking changes as the API and developer experience are still being figured out.
Note: Tidbyt devices are no longer being sold. If you're looking for hardware, Tronbyt appears to be a promising self-hosted alternative (I have purchased one and once I get it I will update this README with my experience):
- Tronbyt Dev Kit (64x32) - Standard size
- Tronbyt Dev Kit Wide (128x32) - Wide version
- JSX Syntax - Write Tidbyt apps using React-like components
- Type-Safe - Full TypeScript support with typed props and configs
- 1:1 API Compatibility - All Pixlet widgets available as JSX components
- Schema System - Type-safe configuration with Zod validation
- Animation Support - Marquee, Animation, Sequence, and frame-based rendering
- Built-in Fonts - BDF font rendering with all standard Tidbyt fonts
- Dev Server - Hot-reloading preview server with WebGL rendering
bun add typlitRequires Bun runtime.
import { Root, Box, Column, Text } from "typlit";
export default function HelloWorld() {
return (
<Root delay={500}>
<Box color="#111">
<Column mainAlign="center" crossAlign="center" expanded>
<Text content="Hello" color="#0ff" />
<Text content="World!" color="#f80" />
</Column>
</Box>
</Root>
);
}# Start the dev server with hot reload
bun run dev
# Run tests
bun testThe dev server runs at http://localhost:3456 and provides a WebGL preview of your app.
All Pixlet render widgets are available:
| Widget | Description |
|---|---|
Root |
Root container (64x32), sets frame delay |
Box |
Container with background color, centers children |
Row |
Horizontal layout |
Column |
Vertical layout |
Stack |
Z-axis stacking (overlapping layers) |
Text |
Single-line text with font support |
WrappedText |
Multi-line wrapped text |
Image |
PNG/GIF image rendering |
Marquee |
Scrolling text/content animation |
Animation |
Frame-based animation from children |
Padding |
Add padding around content |
Circle |
Draw circles |
Plot |
Line/scatter plots |
Sequence |
Multi-page presentations with transitions |
PixelCanvas |
Direct pixel manipulation |
Row and Column support alignment:
<Row mainAlign="center" crossAlign="center" expanded>
{/* mainAlign: start, center, end, space_between, space_around, space_evenly */}
{/* crossAlign: start, center, end */}
</Row>import { Root, Animation, Text } from "typlit";
export default function BlinkingText() {
return (
<Root delay={500}>
<Animation>
<Text content="ON" color="#0f0" />
<Text content="OFF" color="#f00" />
</Animation>
</Root>
);
}import { Root, Marquee, Text } from "typlit";
export default function ScrollingText() {
return (
<Root>
<Marquee width={64} speed={0.5}>
<Text content="This text scrolls across the screen!" color="#fff" />
</Marquee>
</Root>
);
}Define type-safe configuration for your apps:
import { Root, Text, schema, text, toggle, dropdown, color, z } from "typlit";
// Define the schema
export const configSchema = schema({
message: text({ name: "Message", default: "Hello!" }),
showIcon: toggle({ name: "Show Icon", default: true }),
theme: dropdown({
name: "Theme",
options: [
{ display: "Light", value: "light" },
{ display: "Dark", value: "dark" },
] as const,
default: "dark",
}),
textColor: color({ name: "Text Color", default: "#FFFFFF" }),
});
// Infer the config type
type Config = z.infer<typeof configSchema>;
export function MyApp({ config }: { config: Config }) {
return (
<Root>
<Text content={config.message} color={config.textColor} />
</Root>
);
}Available schema fields:
text()- Text inputtoggle()- Boolean toggledropdown()- Select from optionscolor()- Color picker with optional palettedatetime()- Date/time pickerlocation()- Location picker (returns lat/lng/timezone)photo()- Image upload (base64)duration()- Duration picker (e.g., "2s", "500ms")
Built-in fonts from Tidbyt:
tb-8- Default 8px fonttom-thumb- Tiny 4px font6x13- Larger readable font5x8- Compact fontDina_r400-6- Clean sans-serif
<Text content="Hello" font="tom-thumb" color="#fff" />Load fonts before rendering:
import { loadBuiltinFonts, render } from "typlit";
await loadBuiltinFonts();
const frames = await render(<MyApp />);import { render, loadBuiltinFonts } from "typlit";
await loadBuiltinFonts();
const frames = await render(<MyApp />);
// frames is an array of PixelBuffer objectsimport { render, printBuffer, loadBuiltinFonts } from "typlit";
await loadBuiltinFonts();
const frames = await render(<MyApp />);
printBuffer(frames[0], { title: "My App" });See the examples/ directory for complete apps:
hello-world.tsx- Basic layout examplegithub-stargazer.tsx- API integration with marqueeconfigurable-clock.tsx- Schema system demobeat-time.tsx- Swatch Internet Time displaygame-of-life.tsx- Conway's Game of Lifetetris-clock.tsx- Animated tetris-style clockplasma-wave.tsx- PixelCanvas animation demo
src/
├── core/
│ ├── PixelBuffer.ts # RGBA pixel buffer
│ ├── render.ts # JSX tree resolution
│ ├── color.ts # Color parsing
│ └── types.ts # Shared types
├── fonts/
│ ├── parser.ts # BDF font parser
│ └── builtin.ts # Built-in font loading
├── widgets/ # All widget implementations
├── server/ # Dev server with API
├── preview/ # Browser preview renderer
├── schema.ts # Configuration schema system
└── index.ts # Main exports
Typlit aims for 1:1 API compatibility with Pixlet. Here's a conversion example:
Pixlet (Starlark):
load("render.star", "render")
def main():
return render.Root(
child = render.Box(
render.Row(
children = [
render.Text("Hello"),
render.Text("World"),
],
),
),
)Typlit (JSX):
import { Root, Box, Row, Text } from "typlit";
export default function Main() {
return (
<Root>
<Box>
<Row>
<Text content="Hello" />
<Text content="World" />
</Row>
</Box>
</Root>
);
}MIT