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};
struct State { messages: Vec<String>, loading: bool }
fn view(state: &State) -> Elements {
element! {
#(for (i, msg) in state.messages.iter().enumerate() {
Text(key: format!("msg-{i}")) { #(msg.clone()) }
})
#(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]+#[props]— Define components as functions. Props are a struct with#[props]; the function receives props, optional state, hooks, and children, returning anElementstree. Hooks declare behavioral capabilities (events, focus, intervals). -
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 buildingElementsdeclaratively. Supports props, children, keys, conditionals (#(if ...)), loops (#(for ...)), and splicing pre-builtElements(#(expr)). -
Application— Owns your state and manages the render loop.Handlelets 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
| Component | Description |
|---|---|
Text | Styled text with word wrapping (data children: Span) |
Spinner | Animated spinner with auto-tick via lifecycle hooks |
Markdown | Headings, bold, italic, inline code, code blocks, lists |
View | Unified layout container with optional borders, padding, and background |
Canvas | Raw buffer rendering via a user-provided closure |
VStack | Vertical container — children stack top-to-bottom |
HStack | Horizontal 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 using the Hooks API:
Hooks::use_interval— periodic callback (e.g., animation)Hooks::use_mount— fires once after the component is builtHooks::use_unmount— fires when the component is removedHooks::use_autofocus— request focus on mountHooks::use_focus_scope— confine Tab cycling to this subtreeHooks::provide_context— make a value available to descendantsHooks::use_context— read a value provided by an ancestor
§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 component function.
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
| Flag | Default | Description |
|---|---|---|
macros | yes | Enables 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 cells::Cells;pub use children::AddTo;pub use children::ChildCollector;pub use children::ComponentWithSlot;pub use children::DataChildren;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::canvas::Canvas;pub use components::markdown::Markdown;pub use components::markdown::MarkdownState;pub use components::spinner::Spinner;pub use components::spinner::SpinnerState;pub use components::text::Span;pub use components::text::Text;pub use components::text::TextChild;pub use components::view::Direction;pub use components::view::View;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.
- cells
- Cell measurement type for component props. See
Cells. Cell measurement type for component props. - children
- Traits and types for the
element!macro’s child collection system. - component
- The
Componenttrait (framework-internal) and built-in containers (VStack,HStack,Column). - components
- Built-in components:
Text,Spinner,Markdown, andView. Built-in components shipped with eye_declare. - element
- The
Elementslist andElementHandlefor building component trees. - hooks
- Lifecycle hooks for declaring component effects.
- inline
- The
InlineRenderer— low-level inline rendering engine. - insets
- The
Insetstype for declaring content padding and border chrome.
Macros§
- element
- impl_
slot_ children - Implement
ChildCollectorfor a component so it accepts slot children in theelement!macro.
Structs§
- NodeId
- Opaque handle identifying a node in the component tree.
Enums§
- Border
Type - Re-exported from
ratatui_widgetsfor use withView::border. The type of border of aBlock. - Layout
- Layout direction for a container’s children.
- Width
Constraint - How a child claims horizontal space inside an
HStack.
Attribute Macros§
- component
- Declarative element tree macro.
- props
- Declarative element tree macro — the primary way to build UIs in eye_declare.