meowlynxsea/yororen-ui/yororen-ui-state-inputs
Claude Code and agent skill by meowlynxsea/yororen-ui
- community
Checking that the SKILL.md file is still in the repository.
State management and interactive components for end users building Yororen UI apps with gpui. Use when implementing TextInput/TextArea/SearchInput/PasswordInput/NumberInput/FilePathInput/KeybindingInput/ComboBox, building forms (form + form_field + submit_button), wiring on_change/on_submit/on_toggle/on_pick handlers, opening/closing modals/popovers/dropdowns/tooltips/select/menus, configuring the AnimatedVisibility lifecycle on composites, using virtual_list with infinite loading, or debugging typing lag and focus issues. Not for contributing to yororen-ui itself.
Install
npx vskill@latest install meowlynxsea/yororen-ui/yororen-ui-state-inputsSKILL.md
Yororen UI State + Inputs
Yororen UI's inputs and composites are all the same shape under the hood: a headless props builder with a .render(cx) (or .render(cx, window)) that produces a fully wired element. This skill covers the wiring.
1. Mental model
The framework separates what a component knows from who owns its state:
- The component owns interaction state. For a text input, that is
caret position, selection, scroll, blink epoch, IME composition. For a Select, that is open: bool, highlighted_index: usize, animation: AnimatedVisibility. None of this is in your app state.
- Your app owns business state. The text inside the input, the
currently selected option's value, whether the form is currently submitting. You wire the component's on_change / on_pick / on_close callbacks to write into your own Entity<MyState>.
This split is what makes v0.3 inputs work without feedback loops: the component is the single source of truth for interaction state, your app is the single source of truth for business state, and the on_change callback is the only bridge between them.
2. The three render pathways
Every headless factory exposes both .apply(div) and .render(cx). They do very different things.
| API | What it returns | What it does | |
|---|---|---|---|
props.apply(div) | Stateful<Div> | Sets id, track_focus, on_click. No visual feedback. | |
props.render(cx) | Stateful<Div> (or AnyElement for inputs) | Looks up the registered renderer, calls compose, then wires the same a11y callbacks on top. | |
| (Custom) | anything you build | You write the painter yourself (see material_button in layers_demo) |
When to use which
- Default look, fastest path:
.render(cx). The renderer paints
bg / border / padding / radius / hover / active from the theme.
- Caller controls every visual:
.apply(div())...child("Save").
You write the div(), the renderer only contributes the focus ring and click handler.
- Bespoke animation / brand identity: hand-roll a
gpui::Element
(the MaterialRippleElement in layers_demo/src/material_button.rs is the canonical example). You still call props.apply(...) to keep the focus + click wiring.
The layers_demo puts all three in one window — read it to see them side by side.
3. The seven text inputs
All seven share a single state machine (TextInputCore in yororen-ui-core/src/headless/text_input_core.rs): caret, selection, scroll, blink, IME. They differ only in their wrapper UI (placeholder icon, mask char, stepper, browse button, etc.) and their on_change signature.
| Factory | Builder extras | on_change | |
|---|---|---|---|
text_input(id) | .placeholder, .disabled, .max_length | Fn(&str, &mut Window, &mut App) | |
password_input(id) | .mask_char | Fn(&str, &mut Window, &mut App) | |
search_input(id) | .placeholder | Fn(&str, …) + .on_clear(F) (renderer fires after Escape) | |
number_input(id) | .min, .max, .step, .value(seed) | Fn(f64, …) + .on_increment(F), .on_decrement(F) | |
file_path_input(id) | .placeholder | Fn(&str, …) + .on_browse(F) (renderer fires after native picker) | |
text_area(id) | .max_length | Fn(&str, …) (Enter inserts \n) | |
keybinding_input(id) | .mode(Idle or Capturing) | Fn(&str, …) + .on_start_capture(F), .on_cancel_capture(F) |
All seven return AnyElement from .render(cx, window) (they need window for the IME handler registration).
The cx.entity().clone() pattern
Every on_change is a move closure that needs to reach back into your app state. The canonical way is:
Why cx.entity() and not state.global_clone() or anything else:
- It's the only way to get an
Entity<MyApp>from inside a
Context<MyApp> render closure.
- Cloning it is cheap (
Entityis internallyArc). - Each
moveclosure that needs it must clone it once at construction
time; you cannot borrow it.
The inputs_demo (crates/yororen-ui-demos/inputs_demo/src/inputs_app.rs) has all seven inputs wired this way. Copy from it.
Required boot step
text_input::init(cx) binds the keyboard keymap (Backspace, Delete, Left, Right, Shift+Left/Right, Cmd-A/V/C/X, Home, End, Enter, Escape, Ctrl-Cmd-Space for the character palette) to the "UITextInput" context. Call it once at boot, before opening any window that contains a text input. It's idempotent. If you don't call it, the inputs still render but the first keystroke will be silently dropped.
4. Stateful composites
select, combo_box, modal, popover, dropdown_menu, tooltip, listbox, menu, overlay — every one of these is a stateful composite: a headless props builder that takes a caller-owned Entity<XxxState> and a renderer that reads the state's is_open() / is_visible() to decide what to paint.
The lifecycle is uniform:
The shape of every composite state
The renderer is responsible for painting the trigger; the caller is responsible for the on_* callback.
Select + ComboBox — the pick 3-in-1
Both expose a single pick(value, window, cx) method that does set_value + close + invoke_change atomically:
The &mut *cx is the in-place coercion of &mut Context<App> to &mut App (via DerefMut). It's required because pick needs &mut App to schedule the animation tick.
Modal — focus trap + scrim + Escape
The modal renderer wires FocusTrap, Escape, scrim-click for you. You just own the state and the body:
A modal needs to be rendered at the scroll-root level (sibling to your main content, not inside it) and wrapped in gpui::deferred(...).with_priority(2) so it paints above the page content but below the toast host. The gallery_demo shows the exact placement.
Popover + Dropdown + Menu — trigger + content
All three follow the same shape: pass a trigger element, pass a content element, the renderer places the content next to the trigger.
dropdown_menu is the same shape with a built-in items API (state.set_items(vec![...])); menu is the body-only variant for use inside popovers.
Tooltip — delay + placement
TooltipState::set_delay_ms(400) for the show delay; renderer hides on trigger blur or Escape.
5. Form + form_field
Forms are a thin layer over form_field. The form props stores field values + errors; form_field is a labelled wrapper for a single input.
The renderer's job is to lay the fields out (label above input, error below, required marker). Your job is the validation logic in on_submit.
6. Listbox, Tree, Table
Listbox
A scrollable single-select list. The shared keyboard-nav algorithm lives in ListNavigable; ListboxState reuses it via highlight_next / highlight_prev. The renderer paints one row per option and wires each row's click to state.pick(value, …) which writes selected_value and fires on_change.
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
- SKILL.md in the repositoryCheckingcodex-skills/yororen-ui-state-inputs/SKILL.md
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
[](https://verified-skill.com/skills/meowlynxsea/yororen-ui/yororen-ui-state-inputs)