Built-in Components #

eye-declare ships with a small set of built-in components that cover common TUI patterns.

TextBlock #

Styled text with display-time word wrapping. The most common component for displaying text.

Basic usage #

// Simple unstyled text
element! { "Hello, world!" }
// Styled text (imperative)
TextBlock::new()
.line("Bold header", Style::default().add_modifier(Modifier::BOLD))
.line("Normal text", Style::default())
.unstyled("") // empty line

With Line and Span children #

For multi-styled lines, use Line and Span children in the macro:

element! {
TextBlock {
Line {
Span(
text: "Status: ".into(),
style: Style::default().fg(Color::DarkGray)
)
Span(
text: "Online".into(),
style: Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)
)
}
Line {
Span(text: "Last updated: 2 minutes ago".into())
}
}
}

Line and Span are data children — they're collected at build time and stored on the TextBlock, not as child components in the tree.

Word wrapping #

TextBlock automatically wraps text at word boundaries when content exceeds the available width. The wrapping is computed at render time using the actual allocated width, so it responds correctly to terminal resizes.

Properties #

PropertyTypeDescription
(lines)Built via .line() or Line/Span childrenThe text content

Spinner #

An animated Braille spinner with a label. Commonly used to indicate ongoing work.

Usage #

// Basic
element! {
Spinner(label: "Loading...".into())
}
// With key for reconciliation
element! {
Spinner(key: "task-1", label: "Processing...".into())
}
// Done state — shows checkmark instead of animation
element! {
Spinner(key: "task-1", label: "Complete".into(), done: true)
}

Imperative construction #

let spinner = Spinner::new("Loading...")
.done("Completed!")
.label_style(Style::default().fg(Color::Cyan))
.spinner_style(Style::default().fg(Color::Yellow));

How it animates #

The Spinner registers an 80ms use_interval in its lifecycle() method. Each tick advances through a sequence of Braille characters. When done is true, it displays a checkmark symbol instead.

The animation state is preserved across rebuilds (via reconciliation), so changing the label doesn't restart the animation.

Properties #

PropertyTypeDefaultDescription
labelString""Text displayed next to the spinner
doneboolfalseShow checkmark instead of animation
label_styleStyledefaultStyle for the label text
spinner_styleStyledefaultStyle for the spinner symbol

Markdown #

Renders a subset of Markdown with terminal styling.

Usage #

element! {
Markdown(source: "# Heading\n\nThis is **bold** and `inline code`.".into())
}

Supported syntax #

ElementSyntaxDefault style
Heading 1# TitleBold + Cyan
Heading 2## TitleBold + Cyan
Heading 3### TitleBold + Cyan
Bold**text**Bold modifier
Italic*text*Italic modifier
Inline code`code`Yellow
Code block``` ... ```Green
List item- itemGray bullet

Properties #

PropertyTypeDefaultDescription
sourceString""The Markdown text to render

The component maintains a MarkdownState with default styles that can be customized.

VStack #

Vertical container — children stack top-to-bottom.

element! {
VStack {
"First"
"Second"
"Third"
}
}

VStack renders nothing itself — it exists purely to group children with vertical layout. This is the default layout direction, so you only need VStack when you want an explicit named group.

HStack #

Horizontal container — children lay out left-to-right.

element! {
HStack {
Column(width: Fixed(20)) {
"Sidebar"
}
Column {
"Main content"
}
}
}

HStack renders nothing itself. Children declare their widths via WidthConstraint: Fixed(n) for exact columns, Fill (default) for remaining space split equally.

Column #

A width-constrained wrapper for children inside an HStack.

element! {
HStack {
Column(width: Fixed(3)) {
Spinner(label: "".into())
}
Column {
"Takes remaining space"
}
}
}

Properties #

PropertyTypeDefaultDescription
widthWidthConstraintFillHow this column claims horizontal space