SkillAgentSearch skills...

Uibeam

A struct-based, JSX-style Web UI library for Rust

Install / Use

/learn @ohkami-rs/Uibeam

README

<div align="center"> <h1> UIBeam </h1> <p> A struct-based, JSX-style Web UI library for Rust </p> </div> <div align="right"> <a href="https://github.com/ohkami-rs/uibeam/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/crates/l/uibeam.svg" /></a> <a href="https://github.com/ohkami-rs/uibeam/actions"><img alt="CI status" src="https://github.com/ohkami-rs/uibeam/actions/workflows/CI.yml/badge.svg"/></a> <a href="https://crates.io/crates/uibeam"><img alt="crates.io" src="https://img.shields.io/crates/v/uibeam" /></a> </div>
  • UI! : JSX-style template syntax with compile-time checks
  • Beam : Component system based on Rust structs

Features

  • Struct-based component model.
  • Client components via island architecture in Wasm. (experimental) (See Client Component section below)
  • Best effort to generate efficient code for template rendering with less redundant memory allocation.

Additionally, HTML completions and hovers in UI! by VSCode extension. (searchuibeam in the extension marketplace)

Usage

[dependencies]
uibeam = "0.4"

When using uibeam just as a template engine, disabling client default feature is recommended to eliminate useless dependencies:

[dependencies]
uibeam = { version = "0.4", default-features = false }

UI! syntax

use uibeam::UI;

fn main() {
    let user_name = "foo";

    let style = "
        color: red; \
        font-size: 20px; \
    ";
    
    let ui: UI = UI! {
        <p class="hello" style={style}>
            "Welcome to the world of UIBeam!"
            <br>
            "こんにちは"
            <a
                class="user"
                style="color: blue;"
                data-user-id="123"
                href="https://example-chatapp.com/users/123"
            >
                "@"{user_name}"!"
            </a>
        </p>
    };

    println!("{}", uibeam::shoot(ui));
}

unsafely insert HTML string

raw string literal (r#"..."#) or unsafe block contents are rendered without HTML-escape :

<!-- ignore for `include_str!` -->
use uibeam::UI;

fn main() {
    println!("{}", uibeam::shoot(UI! {
        <html>
            <body>
                /* ↓ wrong here: scripts are html-escaped... */

                <script>
                    "console.log('1 << 3 =', 1 << 3);"
                </script>

                <script>
                    {include_str!("index.js")}
                </script>

                /* ↓ scripts are NOT html-escaped, rendered as they are */

                <script>
                    r#"console.log('1 << 3 =', 1 << 3);"#
                </script>

                <script>
                    unsafe {include_str!("index.js")}
                </script>

                <script>
                    unsafe {"console.log('1 << 3 =', 1 << 3);"}
                </script>
            </body>
        </html>
    }));
}

conditional & iterative rendering

{} at node-position in UI! can render, in addition to Display-able values, any impl IntoIterator<Item = UI>. This includes Option<UI> or any other iterators yielding UIs !

use uibeam::{UI, Beam};

struct Task {
    id: u64,
    title: String,
    subtasks: Vec<String>,
    completed: bool,
}

fn main() {
    let t = Task {
        id: 42,
        title: "try uibeam".to_string(),
        subtasks: vec![],
        completed: false,
    };

    let ui = UI! {
        <div id={format!("task-{}", t.id)}>
            <h2>{t.title}</h2>

            <h3>"subtasks"</h3>
            <ul>
                {t.subtasks.iter().map(|s| UI! {
                    <li>{s}</li>
                })}
            </ul>

            {t.completed.then_some(UI! {
                <i><strong>"completed"</strong></i>
            })}
        </div>
    };

    println!("{}", uibeam::shoot(ui));
}

Beam - Component with Rust struct and JSX-like syntax

use uibeam::{Beam, UI};

struct Layout {
    title: String,
    children: UI,  // `children` field
}

impl Beam for Layout {
    fn render(self) -> UI {
        UI! {
            <html>
                <head>
                    <title>{self.title}</title>
                    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css">
                </head>
                <body class="bg-gray-100">
                    {self.children}
                </body>
            </html>
        }
    }
}

struct AdminPage {}

impl Beam for AdminPage {
    fn render(self) -> UI {
        UI! {
            <main class="container mx-auto flex-grow py-8 px-4">
                <section class="bg-white shadow-md rounded-lg p-6">
                    <h1 class="text-2xl font-bold text-gray-800 mb-6">
                        "Password"
                    </h1>
                    <form method="post" action="" class="w-full">
                        <div class="flex flex-col gap-4">
                            <div class="flex flex-col">
                                <label for="adminPassword" class="text-gray-700 text-sm font-bold mb-1">
                                    "password"
                                </label>
                                <input
                                    required
                                    type="password"
                                    id="adminPassword"
                                    name="adminPassword"
                                    class="py-2 px-3 border border-gray-400 rounded focus:outline-none focus:shadow-outline"
                                />
                            </div>
                        </div>
                        <div class="mt-6">
                            <button
                                type="submit"
                                class="bg-purple-500 hover:bg-purple-700 text-white py-2 px-4 rounded focus:outline-none focus:shadow-outline"
                            >
                                "Send"
                            </button>
                        </div>
                    </form>
                </section>
            </main>
        }
    }
}

fn main() {
    let ui = UI! {
        <Layout title="admin page">  // title: ("admin page").into()
            <AdminPage />  // children: (AdminPage {}).render()
        </Layout>
    };

    println!("{}", uibeam::shoot(ui));
}

Client Component - Wasm islands

overview

#[client] makes your Beam a Wasm island : initially rendered on server, sent with serialized props, and hydrated with deserialized props on browser.

Signal, computed, effect, batch, untracked are available in them.

note

EXPERIMENTAL.

Currently UIBeam's hydration/reactivity system is built upon Preact. This will be rewritten in pure Rust in the future.

usage

working example: examples/counter

  1. Activate "client" feature, and add serde to your dependencies:

    [dependencies]
    uibeam = { version = "0.4" }  # `client` is a default feature
    serde  = { version = "1", features = ["derive"] }
    
  2. Configure to export all your client components from a specific library crate. (e.g. lib.rs entrypoint, or another member crate of a workspace)

    (There's no problem if also including ordinary Beams in the lib crate.)

    Additionally, specify crate-type = ["cdylib", "rlib"] for the crate:

    [lib]
    crate-type = ["cdylib", "rlib"]
    
  3. Define and use your client components:

    /* islands/src/lib.rs */
    
    use uibeam::{UI, Beam};
    use uibeam::{Signal, callback, client::PointerEvent};
    use serde::{Serialize, Deserialize};
    
    struct CounterButton {
        on_click: Box<dyn Fn(PointerEvent)>,
        children: UI,
        /// additional classes to modify default style
        class: Option<&'static str>,
    }
    #[uibeam::client] // client component, but not Serialize/Deserialize and not at island boundary
    impl Beam for CounterButton {
        fn render(self) -> UI {
            UI! {
                <button
                    class={self.class.unwrap_or("")}
                    onclick={self.on_click}
                >
                    {self.children}
                </button>
            }
        }
    }
    
    // client component at **island boundary** must be `Serialize + for<'de> Deserialize<'de>`.
    #[derive(serde::Serialize, serde::Deserialize)]
    pub struct Counter {
        pub initial_count: i32,
    }
    // `(island)` means **island boundary**
    #[uibeam::client(island)]
    impl Beam for Counter {
        fn render(self) -> UI {
            let count = Signal::new(self.initial_count);
    
            // callback! - a thin utility for callbacks using signals.
            let increment = callback!(
                // [dependent signals, ...]
                [count],
                // closure depending on the signals
                |_| count.set(*count + 1)
            );
            /* << expanded >>
             
            let increment = {
                let count = count.clone();
                move |_| count.set(*count + 1)
            };
              
            */
    
            let decrement = callback!([count], |_| {
                count.set(*count - 1);
            });
    
            UI! {
                <div>
                    <p>
                        "Count: "{*count}
                    </p>
                    <div>
                        <CounterButton
                            on_click={Box::new(decrement)}
                            class={None}
                        >"-"</CounterButton>
                        <CounterButton
                            o
    

Related Skills

View on GitHub
GitHub Stars78
CategoryDevelopment
Updated28d ago
Forks3

Languages

Rust

Security Score

100/100

Audited on Feb 25, 2026

No findings