meowlynxsea/yororen-ui/yororen-ui-recipes

Claude Code and agent skill by meowlynxsea/yororen-ui

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

End-to-end recipe patterns for end users building gpui apps with Yororen UI (yororen_ui). Use when the user asks for a complete working example, wants a screen layout pattern, or needs guidance composing components, modals, forms, list rendering, virtualized lists, notifications, theme switching, or i18n. Triggers include "show me a full example", "copy from the gallery", "how do I do theme switching", "how do I compose a modal + form", "how do I virtualize a long list". Not for contributing to yororen-ui itself.

Install

npx vskill@latest install meowlynxsea/yororen-ui/yororen-ui-recipes

SKILL.md

Yororen UI Recipes

Prefer copying and adapting proven patterns from the demos rather than inventing new architectures. The five live demos converge on a small set of working patterns; this skill points to the right demo for each need and distills the cross-cutting composition rules.

1. The five demos

All five are under crates/yororen-ui-demos/. The first four are members of the workspace Cargo.toml; the fifth is excluded but still builds in isolation.

DemoWhat it showsCopy from when you want…
counterminimal bootstrap, single Entity<T> global, three buttons, cx.notify()a starter template for any app
layers_demothe 3 render pathways (headless / default-render / caller-custom Material ripple) side by sideto understand the visual flexibility of headless props
inputs_demoall 7 text inputs wired with cx.entity().clone() on_change closuresan input form, a settings page, anything input-heavy
gallery_demothe full 54-component showcase, theme switching, i18n, notification host, virtualized lista kitchen-sink reference for any pattern
theme_showcaseper-render theme::install(cx, theme) for live theme switchinga "Next theme" toolbar, A/B theme testing

The gallery is the most complete — start there if you're building a real app. The counter is the smallest — start there if you're new.

2. Counter — the minimal template

Files: crates/yororen-ui-demos/counter/src/.

rust
// main.rs
use gpui::{App, AppContext, Application, WindowBounds, WindowOptions, px, size};
use yororen_ui::assets::UiAsset;
use yororen_ui::locale_en;
use yororen_ui::renderer;
mod counter_app;
mod state;
fn main() {
let app = Application::new().with_assets(UiAsset);
app.run(|cx: &mut App| {
renderer::install(cx, cx.window_appearance());
locale_en::install(cx);
cx.set_global(state::AppState::new(cx));
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(
gpui::Bounds::centered(None, size(px(400.0), px(300.0)), cx),
)),
..Default::default()
};
let _ = cx.open_window(options, |_, cx| cx.new(|_cx| counter_app::CounterApp));
});
}
rust
// state.rs
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 {}
rust
// counter_app.rs — three buttons, each with its own Entity clone.
use gpui::{Context, IntoElement, ParentElement, Render, Styled, Window, div};
use yororen_ui::headless::button::button;
use yororen_ui::headless::label::label;
use crate::state::AppState;
pub struct CounterApp;
impl Render for CounterApp {
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let count = cx.global::<AppState>().counter.read(cx).value;
let inc = cx.global::<AppState>().counter.clone();
let dec = cx.global::<AppState>().counter.clone();
let reset = cx.global::<AppState>().counter.clone();
div().size_full().flex().flex_col().items_center().justify_center().gap_3().p_4()
.child(label("count", count.to_string(), cx).render(cx).text_size(gpui::px(20.)))
.child(
div().flex().gap_2()
.child(button("dec", cx).on_click(move |_, _, cx| {
dec.update(cx, |c, cx| { c.value -= 1; cx.notify(); });
}).render(cx).child("-"))
.child(button("reset", cx).on_click(move |_, _, cx| {
reset.update(cx, |c, cx| { c.value = 0; cx.notify(); });
}).render(cx).child("Reset"))
.child(button("inc", cx).on_click(move |_, _, cx| {
inc.update(cx, |c, cx| { c.value += 1; cx.notify(); });
}).render(cx).child("+")),
)
}
}

Three takeaways from the counter:

  • The state is one Entity<Counter> inside a Global wrapper. The

global is registered with cx.set_global, read with cx.global::<AppState>().

  • Each button's move closure clones the Entity<Counter> once at

construction. The closure owns the clone.

  • Mutate the value, then cx.notify(). gpui-ce 0.3 does not auto-notify

on Entity::update.

3. Layers demo — the 3 render pathways

Files: crates/yororen-ui-demos/layers_demo/src/.

One window, three columns:

ColumnRender pathwayWhat it shows
1headless::button(id, cx).on_click(...).apply(div().bg(…).p_2().child("click me"))A11y only — caller owns every visual. The button does not respond to hover/press; the cursor changes to a pointer.
2headless::button(id, cx).variant(ActionVariantKind::Neutral).on_click(...).render(cx).child("Click me")The default renderer paints bg / border / padding / radius / hover / active from the theme JSON.
3material_button(id, "Click me".into(), cx, window) (a custom Material-ripple painter defined in material_button.rs)Caller writes a bespoke gpui::Element for the ripple animation. Headless apply is still called for focus + click.

Copy from this demo when you need to:

  • Understand what .apply does and doesn't do (it doesn't paint).
  • See a hand-rolled Element (the ripple) that uses

window.request_animation_frame to advance an f32 progress and PathBuilder to draw a true circle.

  • See how a custom renderer is wired into the headless flow (the

material_button function calls headless::button(id, cx).on_click(...).apply(div()...) internally — the focus + click is the headless layer's job; the visual is the bespoke painter's job).

The demo also has a fourth panel: a text_input with .render(cx, window) to prove the headless / renderer split works the same for inputs, not just buttons.

4. Inputs demo — all 7 text inputs wired

Files: crates/yororen-ui-demos/inputs_demo/src/.

Seven panels, one per text input. The pattern is the same for all:

rust
text_input("demo-text-input")
.placeholder("Type here…")
.on_change({
let entity = cx.entity(); // <-- the canonical pattern
move |new: &str, _window, cx| {
entity.update(cx, |s, _cx| s.text_value = new.to_string());
}
})
.render(cx, window)

Plus a status line that reads the value back from state:

rust
fn status_line(text: &str) -> Div {
div().text_color(hsla(0.0, 0.0, 0.4, 1.0)).text_size(px(12.)).child(text.to_string())
}
// ...
.child(status_line(&format!("text_input value: {:?}", self.text_value)))

Two non-obvious things the demo shows:

  • `number_input` has three callbacks — on_change(f64, …) for

the typed value, on_increment(f64, …) and on_decrement(f64, …) for the stepper buttons. All three write to the same field.

  • `keybinding_input` has its own `mode: KeybindingInputMode`

(Idle or Capturing). The renderer drives the mode transitions but you wire the on_start_capture / on_cancel_capture hooks to update your app state so the status line shows "Capturing…" vs "Idle".

5. Gallery demo — the kitchen sink

Files: crates/yororen-ui-demos/gallery_demo/src/.

By far the largest demo. It is the canonical reference for:

  • The full component surface (54 components, all in one window).
  • cell() and input_cell() helper functions that wrap a single

component in a labelled card with a status line — the fastest way to compose a "compare every variant" page.

  • A working theme switcher in the toolbar (default vs brutalism, light

vs dark) using cx.renderer_arc and per-render theme::install(cx, theme).

  • i18n with multiple locales + app-specific translations layered on

top of the framework strings, via locale::install_with_translations(cx, "en"|"zh-CN"|"ar", app_map).

  • The NotificationCenter host rendered with

gpui::deferred(...).with_priority(3) so toasts float above modals.

  • The modal scrim and Esc handling using modal_state.set_on_close +

ModalCloseReason::Escape | ScrimClick | Programmatic.

  • A virtualized list with infinite loading, using

on_visible_range_change to bump the controller's count.

When the user asks "how do I do X", the answer is almost always in the gallery. Open it before writing new code.

6. Theme showcase — live theme switching

Files: crates/yororen-ui-demos/theme_showcase/src/ (excluded from workspace but still builds; check the file before reaching for it).

Two-window pattern for theme switching:

rust
fn main() {
let app = Application::new().with_assets(UiAsset);
app.run(|cx: &mut App| {
// Register the 54 default renderers ONCE (no theme yet).
default_renderer::install(cx, cx.window_appearance());
let app_entity = cx.new(|_cx| theme_app::ThemeApp::new());
let _ = cx.open_window(options, |_, _cx| app_entity);
});
}
impl Render for ThemeApp {
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// Re-install the theme on every render — cheap, and lets a
// toolbar button advance `self.current` and retheme the window.
theme::install(cx, self.current_theme());
let surface = cx.theme().get_color("surface.base").unwrap_or_default();
// ... layout ...
}
}

The four themes are usually system-light, system-dark, and two inline const CATPPUCCIN: &str = r##"{...}"##; JSON strings. Bump self.current = (self.current + 1) % themes.len() in a click handler, cx.notify(), and the next render re-themes the window.

7. Composition rules

These are the rules the demos converge on. Violate them and the UI becomes hard to maintain or visibly broken.

  • Stable identity. Every stateful child in a list, virtualized

view, or any tree that can be reordered needs .id(...). Use the row index as the id when the data is append-only; use the data's primary key when the data is mutable. Example: text_input(format!("row-email-{ix}")) in a virtual list.

  • Render overlays at the scroll root, not inside a section. A

modal that lives inside a scrollable column will be clipped and won't receive Escape reliably. Build the scroll root, then .child(content).child(modal).child(toast_host) as siblings, with the modal and toast wrapped in gpui::deferred(...).with_priority(N).

  • `&mut cx for minting entities from Context<T>`.**

cx.focus_handle() and XxxState::new(cx) need &mut App. Inside a Render::render closure you have &mut Context<MyApp>; Context<T>: DerefMut<Target = App>, so &mut **cx is a &mut App. Use it inline at the call site, never stored in a let.

  • Stable identifier scope. Two inputs with the same id share

the same keyed state. If you render the same logical input twice (in a list of email rows, say), include the row index or some unique key in the id: format!("email-{row_id}").

  • No `render()` side effects. Render closures must be pure with

respect to gpui's redraw cycle. Spawning tasks, calling cx.spawn, doing file I/O — all of these go in event handlers, not in Render::render.

  • One renderer per `(Marker, dyn Trait)` slot. If you register

BrutalButtonRenderer for the Button marker, the previous TokenButtonRenderer is overwritten. There's no layering. If you want a hybrid, write a new renderer that does both.

8. Choosing a renderer

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
331
Repository forks
9
7-day trend score
29

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/meowlynxsea/yororen-ui/yororen-ui-recipes/badge)](https://verified-skill.com/skills/meowlynxsea/yororen-ui/yororen-ui-recipes)

Something wrong with this skill or its page? Tell us.