Skip to main content

Crate eye_declare

Crate eye_declare 

Source
Expand description

Declarative inline TUI rendering for Rust, built on Ratatui.

eye_declare provides a React-like component model for terminal UIs that render inline — content grows downward into the terminal’s native scrollback rather than taking over the full screen. This makes it ideal for CLI tools, AI assistants, build systems, and interactive prompts where earlier output should remain visible.

§Quick start

use eye_declare::{element, Application, Elements, Spinner, TextBlock};

struct State { messages: Vec<String>, loading: bool }

fn view(state: &State) -> Elements {
    element! {
        #(for (i, msg) in state.messages.iter().enumerate() {
            TextBlock(key: format!("msg-{i}"), lines: vec![msg.clone().into()])
        })
        #(if state.loading {
            Spinner(key: "loading", label: "Thinking...")
        })
    }
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let (mut app, handle) = Application::builder()
        .state(State { messages: vec![], loading: true })
        .view(view)
        .build()?;

    tokio::spawn(async move {
        handle.update(|s| s.messages.push("Hello!".into()));
        handle.update(|s| s.loading = false);
    });

    app.run().await
}

§Core concepts

  • Component — The trait all UI elements implement. Props live on &self (immutable, set by the parent); internal state lives in the associated State type, managed by the framework via Tracked for automatic dirty detection.

  • Elements — A list of component descriptions returned by view functions. The framework reconciles the new list against the existing tree, preserving state for reused nodes.

  • element! — A JSX-like proc macro for building Elements declaratively. Supports props, children, keys, conditionals (#(if ...)), loops (#(for ...)), and splicing pre-built Elements (#(expr)).

  • Application — Owns your state and manages the render loop. Handle lets you send state updates from any thread or async task.

  • InlineRenderer — The lower-level rendering engine for when you need direct control over the render loop (sync code, embedding, custom event loops).

§Built-in components

ComponentDescription
TextBlockStyled text with display-time word wrapping
SpinnerAnimated spinner with auto-tick via lifecycle hooks
MarkdownHeadings, bold, italic, inline code, code blocks, lists
VStackVertical container — children stack top-to-bottom
HStackHorizontal container with WidthConstraint-based layout

§Layout

Vertical stacking is the default. HStack provides horizontal layout where children declare their width via WidthConstraint::Fixed or WidthConstraint::Fill. Components can declare Insets for border/padding chrome — children render inside the inset area while the component draws its chrome in the full area.

§Lifecycle hooks

Components declare effects in Component::lifecycle using the Hooks API:

§Context

The context system lets ancestor components provide values to their descendants without prop-drilling. Register root-level contexts via ApplicationBuilder::with_context, or have components provide them via Hooks::provide_context in their lifecycle method. Descendants read context values with Hooks::use_context.

This is commonly used with Application::run_loop to give components access to an app-domain event channel:

let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let (mut app, handle) = Application::builder()
    .state(MyState::default())
    .view(my_view)
    .with_context(tx)
    .build()?;

let h = handle.clone();
tokio::spawn(async move {
    while let Some(event) = rx.recv().await {
        match event {
            AppEvent::Submit(val) => h.update(|s| s.result = val),
            AppEvent::Quit => { h.exit(); break; }
        }
    }
});

app.run_loop().await?;

§Feature flags

FlagDefaultDescription
macrosyesEnables the element! proc macro via eye_declare_macros

Re-exports§

pub use app::Application;
pub use app::ApplicationBuilder;
pub use app::CommittedElement;
pub use app::ControlFlow;
pub use app::CtrlCBehavior;
pub use app::Handle;
pub use app::KeyboardProtocol;
pub use children::AddTo;
pub use children::ChildCollector;
pub use children::ComponentWithSlot;
pub use children::DataHandle;
pub use children::SpliceInto;
pub use component::Column;
pub use component::Component;
pub use component::EventResult;
pub use component::HStack;
pub use component::Tracked;
pub use component::VStack;
pub use components::markdown::Markdown;
pub use components::markdown::MarkdownState;
pub use components::spinner::Spinner;
pub use components::spinner::SpinnerState;
pub use components::text::Line;
pub use components::text::Span;
pub use components::text::TextBlock;
pub use element::ElementHandle;
pub use element::Elements;
pub use hooks::Hooks;
pub use inline::InlineRenderer;
pub use insets::Insets;

Modules§

app
Application wrapper, builder, handle, and control flow types.
children
Traits and types for the element! macro’s child collection system.
component
The Component trait and built-in container types (VStack, HStack, Column).
components
Built-in components: TextBlock, Spinner, and Markdown. Built-in components shipped with eye_declare.
element
The Elements list and ElementHandle for building component trees.
hooks
Lifecycle hooks for declaring component effects.
inline
The InlineRenderer — low-level inline rendering engine.
insets
The Insets type for declaring content padding and border chrome.

Macros§

element
Declarative element tree macro.
impl_slot_children
Implement ChildCollector for a component so it accepts slot children in the element! macro.

Structs§

NodeId
Opaque handle identifying a node in the component tree.

Enums§

Layout
Layout direction for a container’s children.
WidthConstraint
How a child claims horizontal space inside an HStack.