---
name: freya
description: Freya Rust GUI framework best practices, patterns, and conventions. Use when writing Freya components, hooks, elements, or working on a Freya project.
user-invocable: true
---

# Freya Best Practices

Freya is a cross-platform, native, declarative GUI library for Rust.

General rules:
- Do not write unnecessary comments in code.
- Do not use em dashes.
- Be simple, elegant, and concise.

Start by asking the user what they would like to do:
- Lint and improve existing code
- Refactor existing code
- Add a new feature

## Components

### Struct Components (for stateful UI)

```rust
#[derive(PartialEq)]
struct Counter {
    initial: i32,
}

impl Component for Counter {
    fn render(&self) -> impl IntoElement {
        let mut count = use_state(|| self.initial);
        label()
            .on_mouse_up(move |_| *count.write() += 1)
            .text(format!("Count: {}", count.read()))
    }
}
```

- `#[derive(PartialEq)]` is required - Freya uses it to skip re-rendering unchanged subtrees.
- Implement `KeyExt` and `ChildrenExt` when the component can be keyed or accept children.

#### ComponentOwned (when `render` needs to own `self`)

`Component::render` takes `&self`, so moving fields into closures forces `let foo = self.foo.clone();` boilerplate. `ComponentOwned::render` takes `self` by value (the framework clones it for you), letting you move `self` directly. Requires `#[derive(Clone)]`. Reach for it only when you'd otherwise clone `self` (or several of its fields) inside `render`.

```rust
#[derive(PartialEq, Clone)]
struct Item { state: State<Vec<i32>>, i: usize }

impl ComponentOwned for Item {
    fn render(mut self) -> impl IntoElement {
        Button::new()
            .on_press(move |_| { self.state.write().remove(self.i); })
            .child("Remove")
    }
}
```

### Function Components (app root only)

The app root is a plain function. Hooks like `use_init_theme`, `use_init_radio_station`, `use_provide_context` belong here.

```rust
fn app() -> impl IntoElement {
    rect().child("Hello, World!")
}
```

To pass data from `main` into the root, use the `App` trait:

```rust
struct MyApp { number: u8 }

impl App for MyApp {
    fn render(&self) -> impl IntoElement {
        label().text(self.number.to_string())
    }
}
```

### Utility Functions (stateless, no hooks)

```rust
fn colored_label(color: Color, text: &str) -> impl IntoElement {
    label().color(color).text(text.to_string())
}
```

**Reusable UI that uses hooks or props MUST be a `Component`** (struct + `impl Component`). Plain functions are only for the app root and stateless helpers. Functions with hooks won't benefit from diffing/memoization and can't be keyed or accept reactive props cleanly.

## Elements

Built-in element constructors:

- `rect()` - layout primitive (direction, alignment, sizing, background, borders, corners, shadows, padding, scroll).
- `label()` - single-line text.
- `paragraph()` - multi-line / rich text via `.text_span(...)` children; also the target for `use_editable`.
- `image(handle)` - raster image; `handle` from `static_bytes(...)`, `dynamic_bytes(...)`, or asset loaders.
- `svg(bytes)` - vector image.

`&str` / `String` implement `Into<Label>`, so prefer `rect().child("Hi")` over `rect().child(label().text("Hi"))`.

## Element Builder Pattern

Elements use a fluent builder API. **Never store an element in a variable to modify it later** - chain all methods directly or use `.maybe` / `.map`.

```rust
// Good
rect()
    .background((255, 0, 0))
    .width(Size::fill())
    .height(Size::px(100.))
    .center()       // centers children both axes
    .expanded()     // fills available space in parent's main axis
    .horizontal()   // sets layout direction to horizontal
    .maybe(is_active, |el| el.child("Active"))
    .map(some_value, |el, v| el.child(v.to_string()))

// Bad - storing to modify later
let mut element = rect();
```

Common layout shorthands: `.center()` centers children on both axes; `.expanded()` makes the element fill all remaining space along the parent's main axis (equivalent to `flex: 1` in CSS); `.horizontal()` and `.vertical()` set the layout direction (prefer them over `.direction(Direction::Horizontal/Vertical)`).

### Conditional and Dynamic Rendering

```rust
rect()
    .maybe(show_badge, |el| el.child("New"))          // bool condition
    .map(large_size, |el, size| el.height(size))      // Option<T>, passes value
    .maybe_child(optional_element)                    // Option<impl IntoElement>
```

- `.maybe(bool, |el| el)` - applies the callback when the condition is true
- `.map(Option<T>, |el, val| el)` - applies the callback when the `Option` is `Some`, passing the inner value
- `.maybe_child(Option<impl IntoElement>)` - appends a child only when `Some`

Name the element argument `el` in `.maybe` / `.map` callbacks (not `r`, `rect`, `e`, etc.).

**Prefer one outer `.maybe` / `.map` over several consecutive `.maybe_child` calls gated on the same condition** - it keeps the gating in one place and avoids re-evaluating the same predicate.

```rust
// Good, single .maybe wraps all conditional children
rect()
    .maybe(show, |el| {
        el.child(Title::new("Hi"))
            .child(Content::new().child("Hello"))
            .child(Footer::new())
    })

// Bad, same predicate repeated per child
rect()
    .maybe_child(show.then(|| Title::new("Hi")))
    .maybe_child(show.then(|| Content::new().child("Hello")))
    .maybe_child(show.then(|| Footer::new()))
```

## Events

Attach handlers via builder methods on any element. Handlers receive `Event<T>`; use `move` closures to capture state.

```rust
rect()
    .on_press(move |_| { /* left click, tap, or Enter/Space when focused */ })
    .on_key_down(move |e: Event<KeyboardEventData>| { /* only while focused */ })
    .on_wheel(move |e| { /* scroll delta */ })
    .on_pointer_enter(move |_| { /* hover begin (mouse or touch) */ })
```

Catalog (all prefixed `on_`):

- **Press**: `press` (left/tap/Enter/Space), `all_press` (any mouse button), `secondary_down` (right-click).
- **Mouse**: `mouse_up`, `mouse_down`, `mouse_move`.
- **Pointer** (mouse + touch unified): `pointer_press`, `pointer_down`, `pointer_move`, `pointer_enter`, `pointer_leave`, `pointer_over`, `pointer_out`.
- **Keyboard** (require focus): `key_down`, `key_up`.
- **Wheel**: `wheel`.
- **Touch**: `touch_start`, `touch_end`, `touch_move`, `touch_cancel`.
- **File drop**: `file_drop`.
- **Layout**: `sized` (measured size changed).
- **Global** (no hit-test; use sparingly): `global_pointer_press`, `global_pointer_down`, `global_pointer_move`, `global_key_down`, `global_key_up`, `global_file_hover`, `global_file_hover_cancelled`.
- **Capture** (run before regular handlers): `capture_global_pointer_press`, `capture_global_pointer_move`.

**Prefer `on_press` over raw mouse/pointer events** for interactive elements: it covers click, tap, and keyboard activation, so accessibility comes free. Use `on_mouse_*` / `on_pointer_*` only when you need pointer-specific behavior (drag handles, canvas tools).

`Event<T>` has two cancellation methods, plus `.map(...)` / `.try_map(...)` to transform inner data:

- `.stop_propagation()`: don't bubble this event to ancestor handlers. No effect on events that don't bubble (move/enter/leave, capture, global).
- `.prevent_default()`: don't fire the follow-up events this one triggers (e.g. in `on_mouse_up`, suppresses the `on_pointer_press` and `on_global_pointer_press` that would follow).

### Callback props on custom components

Use `EventHandler<T>` for callback props; closures convert via `.into()`.

```rust
#[derive(PartialEq)]
struct Confirm { on_accept: EventHandler<()> }

impl Component for Confirm {
    fn render(&self) -> impl IntoElement {
        let on_accept = self.on_accept;
        Button::new().on_press(move |_| on_accept.call(())).child("OK")
    }
}

Confirm { on_accept: (move |()| println!("yes")).into() }
```

`EventHandler<T>` is `Copy`; capture directly in `move` closures.

## Focus and Accessibility

Focusable elements need a stable `AccessibilityId` from `use_a11y()` (one per focusable node), attached via `.a11y_id(...)` and `.a11y_focusable(true)`. Track focus state with `use_focus(id)`.

```rust
#[derive(PartialEq)]
struct FocusableBox;

impl Component for FocusableBox {
    fn render(&self) -> impl IntoElement {
        let a11y_id = use_a11y();
        let focus = use_focus(a11y_id);

        rect()
            .a11y_id(a11y_id)
            .a11y_focusable(true)
            .a11y_role(AccessibilityRole::Button)
            .on_press(move |_| println!("activated"))
            .maybe(focus() == Focus::Keyboard, |el| {
                el.border(Border::new().fill(Color::BLUE).width(2.))
            })
            .child("Click or Tab to me")
    }
}
```

`Focus` variants:
- `Focus::Not` - not focused.
- `Focus::Pointer` - focused by mouse/touch (no focus ring needed).
- `Focus::Keyboard` - focused via Tab (render a focus ring).
- `focus.is_focused()` matches `Pointer` or `Keyboard`.

`on_press` already fires on Enter/Space when focused, so keyboard activation is free. For raw key handling, `KeyboardEventExt::is_press_event(&event)` detects the OS activation gesture (Enter/Space; Ctrl+Alt+Space on macOS with VoiceOver).

Other a11y builders: `.a11y_role(...)`, `.a11y_alt("description")`, `.a11y_auto_focus(true)`, `.a11y_member_of(other_id)`, `.a11y_builder(|node| { /* raw accesskit::Node */ })`.

Read the focused id globally via `Platform::get().focused_accessibility_id`.

## Hooks

Hooks are prefixed with `use_` (e.g. `use_state`, `use_animation`). Follow these rules:

1. **Only call hooks at the top level of `render`** - never inside conditionals, loops, or closures.
2. **Hooks must be called in the same order on every render.**
3. **Never call hooks inside event handlers** - call them at the top of `render` and capture the values in `move` closures instead.
4. **Never call hooks inside loops** - the number of hook calls must be constant across renders.
5. **Never call hooks outside of components** - hooks only work inside a `render` method or a function component.
6. **Never call hooks inside async tasks** - `spawn` callbacks are async and cannot call hooks; capture state before spawning. Some hooks have non-hook counterparts that are safe to call in async contexts (e.g. `use_consume` → `consume_context()`).

**Capture hook values in `move` closures** for event handlers:

```rust
let mut state = use_state(|| false);
let on_click = move |_| state.set(true); // capture, not call inside handler
rect().on_mouse_up(on_click)
```

## State Management

### Local State

```rust
let mut count = use_state(|| 0);
*count.write() += 1;          // write
let n = *count.read();        // read
count.set(5);                 // convenience setter
count.set_if_modified(5);     // only writes (and notifies) if the new value differs
```

`use_state` returns a `Copy` type (`State<T>`). No `.clone()` needed when passing it around.

Avoid `drop()` on guards from `.read()`/`.write()`; guards release on scope exit. Prefer a smaller scope (`{ let v = state.read(); ... }`) or copying the value out (`let n = *count.read();`). Only use explicit `drop(guard)` when you must release a borrow before re-borrowing in the same scope.

Prefer `set_if_modified` over `set` when the new value may equal the current (syncing external/derived values, handlers that may fire unchanged). It skips the write and avoids waking subscribers. Requires `T: PartialEq`. `set_if_modified_and_then(value, || { ... })` runs a callback only on actual change.

Pass local state to child components:

```rust
#[derive(PartialEq)]
struct Child(State<i32>);
```

### Global State - Freya Radio

Use Freya Radio for large or deeply nested app state where you need surgical, fine-grained updates - only the components subscribed to a specific channel re-render when that channel changes. This makes it well-suited for complex UIs (e.g. a tab system where each tab has independent state, or a big data model where different parts of the UI subscribe to different slices).

Define your state and a channel enum that maps to the parts of the state that can change independently:

```rust
#[derive(Default, Clone)]
struct AppState {
    count: i32,
    name: String,
}

#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
enum AppChannel {
    Count,
    Name,
}

impl RadioChannel<AppState> for AppChannel {}
```

Initialize once in the root component, then subscribe from any descendant:

```rust
// Root
use_init_radio_station::<AppState, AppChannel>(AppState::default);

// Any component - only re-renders when AppChannel::Count changes
let mut radio = use_radio(AppChannel::Count);
radio.read().count;
radio.write().count += 1;
```

For channels where a write to one should also notify subscribers of another, override `derive_channel`:

```rust
impl RadioChannel<AppState> for AppChannel {
    fn derive_channel(self, _state: &AppState) -> Vec<Self> {
        match self {
            // Writing to Count also notifies Name subscribers
            AppChannel::Count => vec![self, AppChannel::Name],
            AppChannel::Name => vec![self],
        }
    }
}
```

For complex state transitions, implement the reducer pattern with `DataReducer`:

```rust
impl DataReducer for AppState {
    type Channel = AppChannel;
    type Action = AppAction;

    fn reduce(&mut self, action: AppAction) -> ChannelSelection<AppChannel> {
        match action {
            AppAction::Increment => { self.count += 1; }
            AppAction::SetName(n) => { self.name = n; }
        }
        ChannelSelection::Current
    }
}

// Then in a component:
radio.apply(AppAction::Increment);
```

### Readable / Writable (type-erased abstractions)

`Readable<T>` and `Writable<T>` are type-erased wrappers over any reactive value of type `T`. Use them as component props so the component works regardless of where the state lives: local `State<T>`, a `Memo<T>`, a radio slice, or a plain owned value. Both are `PartialEq` (always equal) and `Clone`, usable directly as component fields.

```rust
#[derive(PartialEq)]
struct NameInput { name: Writable<String> }

impl Component for NameInput {
    fn render(&self) -> impl IntoElement {
        Input::new(self.name.clone()) // Input is two-way bound to the Writable
    }
}

// Caller side: any source converts via `into_writable()` / `into_readable()`
NameInput { name: local_name.into_writable() }            // from State<String>
NameInput { name: name_slice.into_writable() }            // from a RadioSliceMut
```

Conversions:
- `State<T>` → `Writable<T>` / `Readable<T>` via `IntoWritable` / `IntoReadable`.
- `Memo<T>` → `Readable<T>` via `IntoReadable`.
- `RadioSlice` → `Readable<T>`; `RadioSliceMut` → `Readable<T>` or `Writable<T>`.
- `Writable<T>` → `Readable<T>` via `From` (downgrade write access).
- Plain owned `T` → `Readable<T>` via `From` (non-reactive; tests, defaults).

API surface:
- `Readable<T>`: `read()` (subscribes), `peek()` (no subscription).
- `Writable<T>`: `read()`, `peek()`, `write()`, plus `WritableUtils` helpers (`set`, `set_if_modified`, `with_mut`, ...). Subscriptions and notifications route to the original source.

Prefer `Readable<T>` for read-only consumers; use `Writable<T>` only when the component must mutate.

### Context API

Use context to make a value available to any descendant component without threading it through every prop. Prefer this over `static` variables, `thread_local!`, or global singletons - context is scoped to the component tree and plays well with Freya's reactivity.

```rust
// Provider: stores the value and makes it available to all descendants
fn app() -> impl IntoElement {
    use_provide_context(|| AppConfig { theme: Theme::Dark });
    rect().child(DeepChild {})
}

// Consumer: retrieve by type, walks up the tree until found
#[derive(PartialEq)]
struct DeepChild;
impl Component for DeepChild {
    fn render(&self) -> impl IntoElement {
        let config = use_consume::<AppConfig>();
        format!("Theme: {:?}", config.theme)
    }
}
```

Use `use_try_consume::<T>()` when the context may not be present. If context is not found, `use_consume` panics.

Context values are identified by type, so each distinct type gets its own slot. Providing the same type again in a deeper component shadows the ancestor's value for that subtree.

Context is the right tool for dependency injection (e.g. passing a DB client, config, or theme down the tree). For reactive shared state use Freya Radio; for passing state between a parent and immediate children, plain props or `State<T>` are simpler.

### Choosing state type

- `use_state` - component-local state
- Context API - dependency injection and non-reactive shared values across the tree; prefer over statics
- Freya Radio - large/nested state, surgical per-channel updates, multi-window
- `Readable`/`Writable` - reusable components that don't care about backing storage

## Derived State and Side Effects

For simple derived values, compute them directly in `render` - no hook needed:

```rust
let doubled = *count.read() * 2;
```

For expensive computations that should only re-run when their dependencies change, use `use_memo`. It subscribes to any `State` read inside the callback and caches the result:

```rust
let expensive = use_memo(move || {
    let n = *count.read(); // subscribed - reruns when count changes
    compute_something(n)
});
let value = expensive.read();
```

For side effects that should re-run when state changes (e.g. logging, triggering external systems), use `use_side_effect`. Do not use it to sync one state into another - derive values directly or use `use_memo` instead:

```rust
use_side_effect(move || {
    let value = *count.read(); // subscribed
    println!("count changed: {value}");
});
```

## Async

### Spawning tasks

Use Freya's `spawn()` (not `tokio::spawn`) for async work that updates the UI. Tasks spawned with `spawn()` are tied to Freya's reactivity system and can safely write to component state:

```rust
let mut data = use_state(|| None);

use_hook(move || {
    spawn(async move {
        let result = fetch_something().await;
        data.set(Some(result));
    });
});
```

`use_hook` runs once on mount, making it the right place for one-shot side effects. `spawn` returns a `TaskHandle` you can cancel if needed.

### Async functions in components

Components and hooks are synchronous - you cannot `await` inside `render`. Prefer `use_future` for typical async work (see below). Only reach for `use_hook` + `spawn` when you need fine-grained control over the task lifecycle:

```rust
// Only when you need manual control
use_hook(move || {
    spawn(async move {
        let s = some_async_fn().await;
        result.set(s);
    });
});
```

### use_future

`use_future` wraps this pattern: it starts an async task on mount and exposes its state as `FutureState<D>` (`Pending`, `Loading`, `Fulfilled(D)`):

```rust
let task = use_future(|| async {
    fetch_user(42).await
});

match &*task.state() {
    FutureState::Pending | FutureState::Loading => "Loading...",
    FutureState::Fulfilled(user) => user.name.as_str(),
}
```

Call `task.start()` to restart and `task.cancel()` to stop it.

### freya-query (cached async data)

For data that should be cached, deduplicated, and automatically refetched, use `freya-query` (`features = ["query"]`):

```rust
// Define the query
#[derive(Clone, PartialEq, Hash, Eq)]
struct FetchUser;

impl QueryCapability for FetchUser {
    type Ok = String;
    type Err = String;
    type Keys = u32;

    async fn run(&self, user_id: &u32) -> Result<String, String> {
        Ok(format!("User {user_id}"))
    }
}

// Use it in a component
impl Component for UserProfile {
    fn render(&self) -> impl IntoElement {
        let query = use_query(Query::new(self.0, FetchUser));

        match &*query.read().state() {
            QueryStateData::Pending => "Loading...",
            QueryStateData::Settled { res, .. } => res.as_deref().unwrap_or("Error"),
            QueryStateData::Loading { .. } => "Refreshing...",
        }
    }
}
```

Multiple components using the same `(capability, keys)` pair share one cache entry. Invalidate with `query.invalidate()` or `QueriesStorage::<FetchUser>::invalidate_all().await`.

For write operations, use `use_mutation` + `MutationCapability`. The `on_settled` callback is the right place to invalidate related queries after a mutation.

Prefer `freya-query` over manual `use_future` + state when you need caching, background refetch, or deduplication.

### Tokio integration

Freya has its own async runtime. To use Tokio-ecosystem crates (`reqwest`, `sqlx`, etc.), enter a Tokio runtime context in `main` before launching:

```rust
fn main() {
    let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
    let _guard = rt.enter(); // keep alive for the whole program

    launch(LaunchConfig::new().with_window(WindowConfig::new(app)))
}
```

Use Freya's `spawn()` for UI updates. `tokio::spawn` runs on the Tokio runtime and **cannot** update component state.

## Theming

Use the theming system to centralize component styling. Prefer it over hand-rolled structs of `Color`/size constants or scattered hardcoded values.

A `Theme` bundles a `ColorsSheet` (the app palette) and per-component `*ThemePreference` entries indexed by string key. Components read their theme via `get_theme!` and resolve color references against the active `ColorsSheet`.

### Provide a theme

Initialize a theme in the root component. The returned `State<Theme>` is reactive - writing to it switches the theme app-wide:

```rust
fn app() -> impl IntoElement {
    let mut theme = use_init_theme(light_theme); // or dark_theme, or your own
    rect()
        .theme_background()
        .theme_color()
        .expanded()
        .center()
        .child(Button::new().on_press(move |_| theme.set(dark_theme())).child("Dark"))
}
```

Use `use_init_root_theme` to register at the root scope. To follow the OS preference, convert `Platform::get().preferred_theme` via the `FromPreference::to_theme` extension.

### Element theme extensions

Built-in elements expose helpers that read the active theme - prefer these over hardcoded colors:

- `rect().theme_background()`, `rect().theme_color()`
- `label().theme_color()`, `paragraph().theme_color()`
- `svg(...).theme_color()` / `.theme_accent_color()` / `.theme_fill()` / `.theme_stroke()` / `.theme_accent_fill()` / `.theme_accent_stroke()`

### Custom themes

Start from `light_theme()` / `dark_theme()` and override what you need. Use `LIGHT_COLORS` / `DARK_COLORS` as base palettes with struct update syntax:

```rust
fn brand_theme() -> Theme {
    let mut theme = dark_theme();
    theme.name = "brand";
    theme.colors = ColorsSheet {
        primary: Color::from_rgb(37, 52, 63),
        secondary: Color::from_rgb(255, 155, 81),
        tertiary: Color::from_rgb(81, 155, 255),
        ..DARK_COLORS
    };
    theme
}
```

`ColorsSheet` covers brand (`primary`/`secondary`/`tertiary`), status (`success`/`warning`/`error`/`info`), surfaces (`background`, `surface_primary`/`secondary`/`tertiary`, `surface_inverse[_secondary|_tertiary]`), borders (`border`, `border_focus`, `border_disabled`), text (`text_primary`/`secondary`/`placeholder`/`inverse`/`highlight`), interaction states (`hover`, `focus`, `active`, `disabled`), and utility (`overlay`, `shadow`).

### Defining a theme for your component

Use `define_theme!` to generate the theme types for a component. This replaces hand-rolled "props struct of colors and sizes" patterns:

```rust
define_theme! {
    %[component]
    pub StatusBadge {
        %[fields]
        background: Color,
        color: Color,
        corner_radius: CornerRadius,
        padding: Gaps,
    }
}

#[derive(PartialEq)]
struct StatusBadge {
    theme: Option<StatusBadgeThemePartial>,
}

impl Component for StatusBadge {
    fn render(&self) -> impl IntoElement {
        let StatusBadgeTheme { background, color, corner_radius, padding } =
            get_theme!(&self.theme, StatusBadgeThemePreference, "status_badge");

        rect()
            .background(background)
            .corner_radius(corner_radius)
            .padding(padding)
            .child(label().text("Active").color(color).font_size(12.))
    }
}
```

The macro generates three structs:
- `StatusBadgeThemePartial` - `Option<Preference<T>>` per field, used for per-instance overrides.
- `StatusBadgeThemePreference` - `Preference<T>` per field, registered in the `Theme` as the default.
- `StatusBadgeTheme` - resolved concrete values, returned by `get_theme!`.

`get_theme!(&self.theme, StatusBadgeThemePreference, "status_badge")` looks up the registered preference, applies any `ThemePartial` override on the instance, and resolves it against the active `ColorsSheet`.

Register the default preference on your custom theme:

```rust
theme.set(
    "status_badge",
    StatusBadgeThemePreference {
        background: Preference::Reference("secondary"),    // resolves from ColorsSheet
        color: Preference::Reference("text_inverse"),
        corner_radius: CornerRadius::new_all(99.).into(),  // Specific via Into
        padding: Gaps::new(4., 10., 4., 10.).into(),
    },
);
```

`Preference::Reference("...")` looks up the named color in the active `ColorsSheet` so the component automatically follows theme switches. Only `Color` fields support references - `Size`, `Gaps`, `CornerRadius`, `f32`, and `Duration` must use `Preference::Specific(v)` (or `v.into()`).

The macro also generates a `StatusBadgeThemePartialExt` trait implemented on the component, giving callers per-field builder methods:

```rust
StatusBadge::new()
    .background(Color::from_rgb(123, 123, 123))   // from the generated Ext trait
    .corner_radius(CornerRadius::new_all(4.))
```

For components that store the partial under a non-default field name (e.g. `theme_colors` alongside `theme_layout`), pass `for = ...; theme_field = ...;` to point the generated builder at the right field. Examples: `Button`, `Card`, `Switch`, `Input` in `crates/freya-components/`.

### When to define a theme

- The component is reusable and could be styled differently across apps or theme switches: use `define_theme!`.
- You currently have a struct of `Color`/size constants that callers tweak: replace it with a theme.
- One-off internal helpers with no styling variation: plain values are fine, no theme needed.

## Keying

Use `.key(id)` on elements in dynamic lists to ensure correct reconciliation on reorders:

```rust
VirtualScrollView::new(|i, _| {
    rect()
        .key(i)
        .child(format!("Item {i}"))
        .into()
})
.length(items.len())
```

Missing `.key()` in dynamic lists causes element misidentification during reorders.

## Internationalization (freya-i18n)

Enable with `features = ["i18n"]`. Uses [Fluent](https://projectfluent.org/) (`.ftl` files) for translations.

**1. Define `.ftl` files:**

```ftl
# en-US.ftl
hello_world = Hello, World!
hello = Hello, { $name }!
```

**2. Initialize once in the root component:**

```rust
use freya::i18n::*;

let mut i18n = use_init_i18n(|| {
    I18nConfig::new(langid!("en-US"))
        .with_locale(Locale::new_static(langid!("en-US"), include_str!("../i18n/en-US.ftl")))
        .with_locale(Locale::new_static(langid!("es-ES"), include_str!("../i18n/es-ES.ftl")))
        .with_fallback(langid!("en-US"))
});
```

**3. Translate in any descendant component:**

```rust
// t! panics if key missing, te! returns Result, tid! falls back to the key string
t!("hello_world")                  // "Hello, World!"
t!("hello", name: {"Alice"})       // "Hello, Alice!"
te!("hello_world")                 // Ok("Hello, World!")
tid!("missing-key")                // "message-id: missing-key should be translated"
```

**4. Switch language at runtime:**

```rust
let mut i18n = I18n::get(); // retrieve from any descendant
i18n.set_language(langid!("es-ES"));
```

For multi-window apps, create with `I18n::create_global` in `main` and share with `use_share_i18n`.

## Animations

Use `use_animation` for manual control and `use_animation_transition` to animate between two values reactively:

```rust
// Manual: call .start() / .reverse() yourself
let mut anim = use_animation(|_| AnimColor::new((240, 240, 240), (200, 80, 80)).time(400));
rect().background(&*anim.read()).on_press(move |_| anim.start())

// Transition: re-runs automatically when the tracked value changes
let color = use_animation_transition(is_active, |from, to| AnimColor::new(from, to).time(300));
rect().background(&*color.read())
```

Animate colors (`AnimColor`), sizes, positions, and other numeric properties. Easing functions and sequencing are supported.

## Routing

Enable with `features = ["router"]`. Define routes with `#[derive(Routable)]`, render them with `router::<Route>()`, place the current page with `outlet::<Route>()`, and navigate with `Link` or `RouterContext::get().replace(...)`:

```rust
#[derive(Routable, Clone, PartialEq)]
enum Route {
    #[route("/")]
    Home,
    #[route("/settings")]
    Settings,
}

fn app() -> impl IntoElement {
    router::<Route>(|| RouterConfig::default())
}
```

## Headless Testing

`freya-testing` lets you test components without a window. Use `TestingRunner` to mount a component, simulate interactions, and assert on state:

```rust
use freya_testing::prelude::*;

let (mut runner, state) = TestingRunner::new(app, (300., 300.).into(), |r| {
    r.provide_root_context(|| State::create(0))
}, 1.);

runner.sync_and_update();
runner.click_cursor((15., 15.));
assert_eq!(*state.peek(), 1);
```

Call `runner.render_to_file("out.png")` to snapshot the current UI.

## Icons

Enable with `features = ["icons"]`. Uses Lucide icons rendered as SVGs:

```rust
use freya::icons;

svg(icons::lucide::antenna()).color((120, 50, 255)).expanded()
```

## Rich Text Editing

Use `use_editable` to manage a text editor with cursor, selection, keyboard shortcuts, and virtualization. Wire it to a `paragraph()` element's event handlers and feed `EditableEvent`s from mouse/keyboard events. See `examples/` for full wiring.

## Code Editor

Enable with `features = ["code-editor"]`. `CodeEditorData` holds a `Rope`-backed buffer with tree-sitter syntax highlighting. You bring your own tree-sitter grammar and highlights query via `EditorLanguage`, so any language can be supported (add the grammar crate, e.g. `tree-sitter-rust`, as a dependency). Pass `None` to disable highlighting. Then pass the data to the `CodeEditor` component:

```rust
let editor = use_state(|| {
    let language = EditorLanguage::new(
        tree_sitter_rust::LANGUAGE,
        tree_sitter_rust::HIGHLIGHTS_QUERY,
    );
    let mut e = CodeEditorData::new(Rope::from_str(src), language);
    e.parse();
    e.measure(14., "Jetbrains Mono");
    e
});
CodeEditor::new(editor, focus.a11y_id())
```

## Plotting

Enable with `features = ["plot"]`. Use the `plot()` element with a `RenderCallback` and draw into it using the Plotters API via `PlotSkiaBackend`:

```rust
plot(RenderCallback::new(|ctx| {
    let backend = PlotSkiaBackend::new(ctx.canvas, ctx.font_collection, size).into_drawing_area();
    // ... Plotters drawing code
})).expanded()
```

## Material Design

Enable with `features = ["material-design"]`. Adds style modifiers like `.ripple()` to built-in components:

```rust
use freya::material_design::*;
Button::new().ripple().child("Click me")
```

## WebView

Enable with `features = ["webview"]`. Embeds a browser view into your UI:

```rust
use freya::webview::*;
WebView::new("https://example.com").expanded()
```

## Terminal

Enable with `features = ["terminal"]`. Spawns a PTY process and renders it as a terminal:

```rust
use freya::terminal::*;
let mut cmd = CommandBuilder::new("bash");
cmd.env("TERM", "xterm-256color");
let handle = TerminalHandle::new(TerminalId::new(), cmd, None).ok();
// Render with Terminal::new(handle) and forward keyboard events via handle.write_key()
```

## Camera

Enable with `features = ["camera"]`. Streams frames from a webcam into reactive state:

```rust
use freya::camera::*;
let camera = use_camera(CameraConfig::default);
CameraViewer::new(camera)
```

On macOS, call `freya::camera::init()` from `main` to request authorization before launching.

## Developer Tools

Enable with `features = ["devtools"]`. Adds a real-time component tree inspector. Run the devtools app alongside your app to examine layout, props, and state.

## Crate Features

Add to your `Cargo.toml` as needed:

```toml
freya = { version = "...", features = ["router", "radio"] }
```

| Feature | What it enables |
|---|---|
| `router` | Page routing (`freya-router`) |
| `i18n` | Internationalization via Fluent (`freya-i18n`) |
| `remote-asset` | Load images/assets from remote URLs |
| `radio` | Global state management (`freya-radio`) |
| `query` | Async data fetching with caching (`freya-query`) |
| `sdk` | Generic utility APIs (`freya-sdk`) |
| `plot` | Chart/plotting via Plotters (`freya-plotters-backend`) |
| `gif` | Animated GIF support in `GifViewer` |
| `calendar` | `Calendar` date-picker component |
| `markdown` | `Markdown` renderer component |
| `icons` | SVG icon library via Lucide (`freya-icons`) |
| `material-design` | Material Design theme (`freya-material-design`) |
| `webview` | Embed a WebView (`freya-webview`) |
| `terminal` | Terminal emulator (`freya-terminal`) |
| `code-editor` | Code editing APIs (`freya-code-editor`) |
| `camera` | Webcam capture (`freya-camera`) |
| `tray` | System tray support |
| `titlebar` | Custom window titlebar component |
| `devtools` | Developer tools overlay |
| `performance` | Performance monitoring plugin |
| `hotpath` | Hot-path optimization |
| `all` | All of the above (except devtools/performance/hotpath) |

## Further Reference

- `AGENTS.md` (also symlinked as `CLAUDE.md`) in the repo root - authoritative dev workflow and Rust conventions for working on Freya itself.
- `crates/freya/src/_docs/` - in-source documentation for hooks, state management, components, routing, animations, and more.
- `examples/` - 150+ working examples covering every feature.
