# Ribir Style And Cleanliness

> Specialized for code style and cleanliness within the Ribir UI framework. Use when working with Ribir DSL (@, rdl!, pipe!), state management ($read, $write, part_writer), or performance optimizations. Use when this capability is needed.

- Skill: `tomevault-io/ribir-style-and-cleanliness` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/ribir-style-and-cleanliness`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/ribir-style-and-cleanliness/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/ribir-style-and-cleanliness

---


# Ribir Style & Cleanliness Guide

This guide ensures that all Ribir code generated by agents meets expert-level standards for readability, performance, and maintainability.

## 1. DSL Elegance (The $ & @ Syntax)

### 1.1 Prefer the `@` Symbol
Within `rdl!` and related macros (e.g., `fn_widget!`, `widget!`), prioritize using `@` to declare widgets instead of the raw `rdl!` syntax.
- ✅ **Recommended**: `@Flex { ... }`
- ❌ **Avoid**: `rdl! { Flex { ... } }`

### 1.2 `$` / `@` Syntax Boundary
`$read(s)`, `$write(s)`, and `@Widget { ... }` are only valid inside Ribir macro context. Standard Rust code cannot use them directly, even inside closures or helper functions unless those expressions are written inside `fn_widget!`, `rdl!`, `widget!`, or another Ribir DSL macro.

Only the `$` family and `@` syntax have this boundary. Ordinary macros, function-style helpers such as `fn_widget!`, `button!`, `text!`, `flex!`, `container!`, and APIs such as `part_writer` can be used wherever normal Rust code allows them.

For simple one-line child calls or macro invocations, prefer removing unnecessary braces:
- `@ { my_widget() }` -> `@my_widget()`
- `@ { some_macro!(...) }` -> `@some_macro!(...)`
- Do not apply this rule to a bare name or single variable such as `@ { child }`.
- Keep `@ { ... }` when the expression is multi-line, contains local bindings or control flow, is a bare name, or is clearer with an explicit block.

- **Avoid Borrow Conflicts**: Never mix `$read` and `$write` on the same state within a single expression (this causes a Rust `RefCell` runtime panic).
- **Do Not Assume Closure Context Is Enough**: `move |_| ...` is still plain Rust unless the closure appears inside a Ribir macro body.

### 1.3 Mandatory `move` Capture
Always explicitly use the `move` keyword in all event closures (e.g., `on_tap`, `on_pointer_move`).
- ✅ **Recommended**: `on_tap: move |_| ...`
- ❌ **Avoid**: `on_tap: |_| ...` (leads to lifetime conflicts)

### 1.4 Automatic State Capture (Prefer Automatic Capture)
Within Ribir macros, prioritize the automatic capture mechanisms provided by the framework. Avoid manual calls to `.clone_writer()`, `.clone_reader()`, or manually defining clone variables outside the macro.

The macros automatically handle capture and lifecycle for:
- **Read/Write**: `$read(s)`, `$write(s)`, `$writer(s)`
- **Subscription/Observation**: `$reader(s)`, `$watcher(s)`
- **Reference Cloning**: `$clone(s)`

- ✅ **Recommended (Idiomatic: Concise & Declarative)**:
  ```rust
  button! {
    on_tap: move |_| *$write(cnt) += 1,
    @ { pipe!($read(cnt).to_string()) }
  }
  ```
- ❌ **Avoid (Non-idiomatic: Verbose boilerplate)**:
  ```rust
  let c_cnt = cnt.clone_writer(); // Avoid manual cloning
  button! {
    on_tap: move |_| *c_cnt.write() += 1, 
    @ { pipe!(...) }
  }
  ```

### 1.5 Use Widget Function Macros For Root Widgets
Prefer the most direct widget function macro when a helper function returns a single root widget tree. Use `button!`, `text!`, `flex!`, `container!`, and similar helpers when they make the root flatter. Most common widgets already have a corresponding function macro; the list in this guide is illustrative, not exhaustive.

Inside an existing DSL tree, prefer normal child declarations such as `@FilledButton { ... }` instead of switching to `@some_macro!` just because a function macro exists.

Use `fn_widget!` only when you need a small DSL scope for local bindings, helper values, or `$`-based reads around the root tree.

When a function macro name conflicts with another macro in scope, qualify it explicitly.

- ✅ **Recommended (Root helper stays flat)**:
  ```rust
  fn title() -> Widget<'static> {
    flex! {
      direction: Direction::Horizontal,
      align_items: Align::Center,
      @Text { text: "Hello" }
      @pipe!($read(cnt).to_string())
    }
    .into_widget()
  }
  ```
- ✅ **Recommended (Nested child keeps declarer form)**:
  ```rust
  @FilledButton {
    on_tap: move |_| submit(),
    @ { "Submit" }
  }
  ```
- ❌ **Avoid (Unnecessary nesting at root)**:
  ```rust
  fn title() -> Widget<'static> {
    fn_widget! {
      @Flex {
        direction: Direction::Horizontal,
        align_items: Align::Center,
        @Text { text: "Hello" }
      }
    }
    .into_widget()
  }
  ```
- ❌ **Avoid (Switching child style without a reason)**:
  ```rust
  @filled_button! {
    on_tap: move |_| submit(),
    @ { "Submit" }
  }
  ```

### 1.6 Prefer Direct Layout Macros For Root Widgets
`flex!`, `stack!`, and similar layout macros are the preferred form when a widget can be expressed as a single root layout tree.

Use `fn_widget!` only when you need Rust logic before the tree description, such as local bindings, control flow, computed values, or state setup.

- ✅ **Recommended**: direct layout macro for a simple widget tree
  ```rust
  fn title() -> Widget<'static> {
    flex! {
      direction: Direction::Horizontal,
      @Text { text: "Title" }
    }
    .into_widget()
  }
  ```
- ❌ **Avoid**: wrapping a single root layout tree in `fn_widget!` without a reason
  ```rust
  fn title() -> Widget<'static> {
    fn_widget! {
      @Flex {
        direction: Direction::Horizontal,
        @Text { text: "Title" }
      }
    }
    .into_widget()
  }
  ```

### 1.7 DSL Minimization & Logic Separation
DSL should only describe **UI tree structure** and **declarative property bindings**. If logic does not involve specific DSL transformations (like `$read`, `pipe!`, `@`) or is complex, extract it outside the macro as standard Rust code.
When you do need state reads inside DSL, place the read as close as possible to the expression that consumes it instead of hoisting it upward.
If the only local binding exists for one child list or subtree, prefer placing it inside that specific `@ { ... }` block rather than wrapping the whole widget in `fn_widget!`.

- ✅ **Recommended (Decoupled Logic & View)**:
  ```rust
  let display_text = if user.is_logged_in() {
      format!("Welcome, {}!", user.name())
  } else {
      "Please log in.".to_string()
  };

  text! {
    text: display_text,
    foreground: Color::BLACK,
  }
  ```
- ❌ **Avoid (Complex logic inside DSL)**:
  ```rust
  text! {
    text: { 
      if user.is_logged_in() {
          format!("Welcome, {}!", user.name())
      } else {
          "Please log in.".to_string()
      }
    },
    foreground: Color::BLACK,
  }
  ```
- ✅ **Recommended (Read close to use)**:
  ```rust
  flex! {
    @ {
      let rows = $read(this).max_rounds();
      (0..rows).map(|row| row_widget(row))
    }
  }
  ```

## 2. State & Performance

### 2.1 State Slicing & Performance
When dealing with long lists or deep UI trees, consider using `part_writer` for state slicing. This ensures sub-widgets only depend on the specific data they need, avoiding unnecessary re-renders.

- ✅ **Recommended (For complex lists or high-performance scenarios)**:
  ```rust
  // Sub-widget depends only on this specific item
  let task = $writer(this).part_writer(
    "task_name".into(),
    move |todos| PartMut::new(todos.get_task_mut(id).unwrap()),
  );
  ```

### 2.2 Reactive Streams
- Prefer `distinct_pipe!` over `pipe!` unless you specifically need to process duplicate values (helps reduce redundant UI updates).

### 2.3 Lightweight Smart Pointers
- When a weak count is not needed, prioritize using the repository's internal `Rc` and `Arc` smart pointers (re-exported from `rclite` in `ribir_algo`) instead of `std::rc::Rc` or `std::sync::Arc`. They are more lightweight and have better performance as they do not support weak counts.

## 3. Debuggability

### 3.1 Recommendation for `debug_name`
For key interactive widgets or those requiring frequent debugging, adding a `debug_name` is recommended. This significantly improves efficiency when using MCP debugging tools, allowing direct interaction via stable names (e.g., `name:counter_button`).

- ✅ **Recommended (For interactive components)**:
  ```rust
  button! {
    debug_name: "submit_button",
    on_tap: move |_| { ... }
  }
  ```

## 4. Automation

Before committing any code changes, the following command sequence must be executed:
1. `cargo +nightly ci check` (Compile verification)
2. `cargo +nightly ci lint` (Static analysis, includes formatting)
3. `cargo +nightly ci test` (Logic verification)

---
> Source: [RibirX/Ribir](https://github.com/RibirX/Ribir) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-24 -->

