Widget authoring boundary

The public authoring boundary is impl From<Component> for Widget.
A component is an ordinary Rust struct. To make it renderable, implement From<Component> for Widget and return the closed Widget tree that describes the interface for the current build inputs.
use fission::prelude::*;

pub struct SaveDraftButton;

impl From<SaveDraftButton> for Widget {
    fn from(_: SaveDraftButton) -> Widget {
        let (ctx, _) = fission::build::current::<ComposerState>();
        let save = with_reducer!(ctx, SaveDraft, save_draft);

        Button {
            on_press: Some(save),
            child: Some(Text::new("Save draft").into()),
            ..Default::default()
        }
        .into()
    }
}
That is the entire public shape. There is no public Widget trait to implement, and user code should not return internal lowering types. The authored component converts into Widget; the runtime and shells handle lowering after that.

Component structs

Use component fields for props: values supplied by the parent for this conversion.
pub struct UserBadge {
    pub display_name: String,
    pub compact: bool,
}
A parent constructs the component and converts it into a child slot:
Column {
    children: widgets![
        UserBadge {
            display_name: "Ada".to_string(),
            compact: false,
        },
    ],
    ..Default::default()
}
Prefer real component structs over helper functions that directly return Widget. Helper functions are fine for formatting and tiny internal conveniences, but production widget trees should be made from named components so examples, docs, tests, and generated code all teach the same pattern.

Child slots store Widget

First-party widgets accept children as closed Widget values. Depending on the widget, that may be Widget, Option<Widget>, or Vec<Widget>.
Use .into() at composition boundaries:
Container::new(Text::new("Hello").into()).into()
Use widgets![...] for literal child lists when you want to avoid repeated .into() calls:
Column {
    children: widgets![
        Text::new("Title"),
        Text::new("Body"),
    ],
    ..Default::default()
}
.into()
For dynamic lists, normal Rust iterators are the right tool:
let children = items
    .into_iter()
    .map(|item| ItemRow { item }.into())
    .collect();

Column {
    children,
    ..Default::default()
}
.into()

Build handles

Use fission::build::current::<State>() inside conversion when the component needs current app inputs or runtime wiring.
let (ctx, view) = fission::build::current::<AppState>();
ViewHandle reads state, environment, runtime values, and layout snapshots. BuildCtxHandle binds actions, local reducers, resources, portals, media, and animation requests.
The handles are valid only during the active build pass. Do not store them for later use.

Local widget state

If a component owns retained UI-local state, mark it with #[fission_component] and annotate local fields:
#[fission_component]
pub struct ToggleChip {
    pub label: String,

    #[local_state(default = false)]
    selected: bool,
}
The macro generates selected() as a local StateField<bool> accessor. Bind local changes with ctx.bind_local(...) and a reducer. Local state is retained by widget identity, not by keeping the component struct instance alive.

Stable identity

Use .id(WidgetId::explicit(...)) when a component with local state appears in dynamic data that can be inserted, removed, filtered, or reordered.
TaskRow { task: task.clone() }
    .id(WidgetId::explicit(&format!("task.{}", task.id)))
You do not need explicit ids for every widget. Use them when identity affects retained local state, animation continuity, previous layout reads, or test targeting.

Providers

A parent can provide typed read-only context to descendants:
Provider::new(FormSection("shipping"), || ShippingFields).into()
A descendant reads it during conversion:
let section = fission::build::read::<FormSection>();
Provider values are scoped to the subtree and resolved by nearest active provider. They are not durable state.

Contract completeness checklist

Use this checklist when wiring Widget authoring boundary into an app or shell implementation.
Requirement
Why it matters
Public entrypoint
The app code must call the documented helper, command, or configuration field rather than a platform-specific shortcut.
Required setup
Native manifests, plist entries, entitlements, service-worker files, domains, protocols, credentials, or provider registration must be present before production use.
Typed success and error
The implementation should return a truthful typed result. Silent success, swallowed errors, and string-only failure paths make apps unsafe to retry.
Retry behavior
Operations that can fail because of permission, network, hardware, store, or packaging state should document whether retrying is useful and what must change first.
Tests
Cover the happy path, unsupported host path, bad configuration path, and any security-sensitive rejection path.

Failure behavior and safe retry

Treat failures as product states, not surprises. A Fission app should be able to tell the user whether the problem is missing configuration, missing provider support, denied permission, unavailable hardware, invalid input, expired credentials, or a transient host error. Reducers should update state from typed results and keep retry buttons idempotent so pressing them twice cannot corrupt state or duplicate unsafe work.
Read State, handles, and providers for the full guide to build handles, provider scopes, local widget state, and WidgetId. Read State system for the exact state and reducer contracts. For common first-party child containers, see Column and Row.
Fission
A cross-platform, GPU-accelerated user interface framework for Rust. MIT licensed.
Copyright (c) 2026 Fission
Ready to use today. Widget APIs are expected to remain stable; some runtime and shell APIs may change before 1.0.0.
main - v0.1.0 alpha