Skip to content

Guide Quick Start

MeowLynxSea edited this page Jun 13, 2026 · 9 revisions

Quick start

Note: This page assumes Yororen UI v0.3, the headless-API rewrite. Every primitive is a headless factory that returns a props builder (button, text_input, virtual_list, modal, …). The visual layer is chosen separately via renderer::install(cx, …). The defaults work out of the box; the brutalism renderer is a one-line swap; and you can author your own by registering renderers against the markers::* trait slots (see Guide-Theming).

This page walks through the 6-step bootstrap that every v0.3 app converges on, then ends with a complete main.rs you can copy.

1) Register the asset source

Icons ship as embedded SVG via UiAsset. Register once when constructing Application:

use gpui::Application;
use yororen_ui::assets::UiAsset;

let app = Application::new().with_assets(UiAsset);

If you ship your own icon set, implement AssetSource (or compose multiple sources with CompositeAssetSource) and pass that to with_assets instead.

2) Install the renderer + theme

One call. This loads system-light.json or system-dark.json (chosen by cx.window_appearance()), installs the global Theme, and registers the 54 default XxxRenderer impls (one per headless component) against the core RendererRegistry.

use gpui::App;
use yororen_ui::renderer;

fn init_renderer(cx: &mut App) {
    renderer::install(cx, cx.window_appearance());
}

Swap to brutalism (sharp corners, hard shadows, monospace) with yororen_ui::brutalism_renderer::install(cx). Pass your own JSON theme with default_renderer::install_with(cx, my_theme). See Guide-Theming for both.

3) Bind the text-input keymap

If your app uses any text input — text_input, password_input, search_input, number_input, file_path_input, text_area, keybinding_input, or combo_box — call this once at boot, before opening any window that contains a text input. It's idempotent (via a OnceLock).

yororen_ui::headless::text_input::init(cx);

The keymap binds Backspace, Delete, Enter, Escape, Left, Right, Shift+Left, Shift+Right, Cmd-A/V/C/X, Home, End, and Ctrl-Cmd-Space (character palette) to the "UITextInput" context. Skip it if your app has its own global keymap that conflicts — the inputs still render, you just lose the default key bindings.

4) Install a locale

Three locales ship out of the box:

Locale Crate Short form
English yororen-ui-locale-en yororen_ui::locale_en::install(cx)
Simplified Chinese yororen-ui-locale-zh-CN yororen_ui::locale_zh_cn::install(cx)
Arabic (RTL) yororen-ui-locale-ar yororen_ui::locale_ar::install(cx)

Use yororen_ui::locale::install_locale(cx, "en" \| "zh-CN" \| "ar") if you'd rather drive it from a string.

For your app's own strings, layer them on top with locale::install_with_translations(cx, "en", app_map) — see codex-skills/yororen-ui-app-core §5.

5) Optional: NotificationCenter

Only set this if your app shows toasts:

use yororen_ui::notification::center::NotificationCenter;

cx.set_global(NotificationCenter::new());

The center is a thin state machine — it owns the queue, schedules auto-dismiss timers, and exposes items() for the renderer to paint. Your app is still responsible for wiring the toast host into the render tree (see Guide-Notification).

6) Optional: your app's global state

Yororen UI apps use gpui::Entity<T> for state. Wrap it in a Global so any render closure can read it via cx.global::<AppState>():

use gpui::{App, AppContext, Entity, Global};

#[derive(Default)]
pub struct Counter { pub value: i32 }

pub struct AppState { pub counter: Entity<Counter> }

impl AppState {
    pub fn new(cx: &mut App) -> Self {
        Self { counter: cx.new(|_| Counter::default()) }
    }
}
impl Global for AppState {}

Then cx.set_global(AppState::new(cx)). Skip this step if your app is small enough to read everything off the root Entity<MyApp>.

7) Open the main window

use gpui::{App, AppContext, Application, WindowBounds, WindowOptions, size, px};

let options = WindowOptions {
    window_bounds: Some(WindowBounds::Windowed(
        gpui::Bounds::centered(None, size(px(800.0), px(600.0)), cx)
    )),
    ..Default::default()
};
let _ = cx.open_window(options, |_, cx| cx.new(|_cx| my_app::MyApp));

The third argument to cx.new is the root Entity<MyApp>MyApp: Render is the type whose Render::render paints the window. cx.entity() (called from inside its render closure) gives you the Entity<MyApp> you can clone into every move closure.

The whole main.rs

A complete, copy-pasteable bootstrap — wired exactly the way crates/yororen-ui-demos/counter/ does it:

use gpui::{App, AppContext, Application, WindowBounds, WindowOptions, size, px};
use yororen_ui::assets::UiAsset;
use yororen_ui::locale_en;
use yororen_ui::notification::center::NotificationCenter;
use yororen_ui::renderer;

mod my_app;
mod state;

fn main() {
    let app = Application::new().with_assets(UiAsset);

    app.run(|cx: &mut App| {
        // 1) Renderer + theme (54 default XxxRenderer impls — see
        //    crates/yororen-ui-core/src/renderer/markers.rs for the
        //    canonical list).
        renderer::install(cx, cx.window_appearance());

        // 2) Text-input keymap (idempotent — safe to call twice).
        yororen_ui::headless::text_input::init(cx);

        // 3) Locale.
        locale_en::install(cx);

        // 4) Notification center (only if your app uses toasts).
        cx.set_global(NotificationCenter::new());

        // 5) App global state (skip if not needed).
        cx.set_global(state::AppState::new(cx));

        // 6) Main window.
        let options = WindowOptions {
            window_bounds: Some(WindowBounds::Windowed(
                gpui::Bounds::centered(None, size(px(800.0), px(600.0)), cx)
            )),
            ..Default::default()
        };
        let _ = cx.open_window(options, |_, cx| {
            cx.new(|_cx| my_app::MyApp)
        });
    });
}

The corresponding minimal state.rs and my_app.rs are in the counter demo (crates/yororen-ui-demos/counter/). Use it as your template.

Verifying the bootstrap

After wiring, run through this list before moving on:

  • cargo check is clean
  • The window opens at the expected size
  • A simple headless::button with .render(cx) shows up with the default colors (proves renderer + theme are wired)
  • cx.theme().get_color("surface.base") returns Some(_) (proves the theme is loaded)
  • cx.t("common.save") returns the expected string (proves i18n is loaded)
  • If using text inputs, init(cx) was called and the first keystroke inserts text (proves the keymap is bound)

If any of these fail, the issue is almost always out-of-order init — themed component rendered before install ran, or cx.set_global called after the first Render.

See also

Clone this wiki locally