Skip to content
wirelessEye edited this page Jun 29, 2026 · 8 revisions

Props

The #[props] macro turns a named-field struct into a Nestix prop container and typed builder.

#[props(debug)]
#[derive(Debug)]
pub struct ButtonProps {
    #[props(default)]
    disabled: bool,
    on_click: Option<Shared<dyn Fn()>>,
}

Every normal field is transformed into PropValue<T>. Component code reads the current value with .get().

if props.disabled.get() {
    // disabled
}

Required Fields

Fields are required unless they are Option<T> or marked with #[props(default)] or #[props(start)].

#[props]
pub struct TextProps {
    text: String,
}

layout! {
    Text(.text = "Hello".to_string())
}

The generated builder tracks required fields at the type level.

Optional Fields

Option<T> fields default to None automatically.

#[props]
pub struct LinkProps {
    href: String,
    target: Option<String>,
}

Callers may omit .target.

Default Fields

Use #[props(default)] to default through Default::default().

#[props]
pub struct InputProps {
    #[props(default)]
    value: String,
}

Use #[props(default = expr)] for a custom default.

#[props]
pub struct BadgeProps {
    #[props(default = "neutral".to_string())]
    tone: String,
}

Start Fields

Use #[props(start)] for positional arguments in layout! and build_props!.

#[props(debug)]
#[derive(Debug)]
pub struct TextProps {
    #[props(start)]
    text: String,
}

layout! {
    Text("Hello")
}

Start fields are passed to the generated builder constructor before named properties.

Debug Props

#[props(debug)] makes the generated Props::debug_fmt implementation delegate to the struct's Debug implementation.

#[props(debug)]
#[derive(Debug)]
pub struct PanelProps {
    #[props(default)]
    children: Layout,
}

Without debug, type-erased props format as Props(..).

Generic Bounds

Use #[props(bounds(...))] to provide bounds for generated implementations.

#[props(bounds(I: IntoIterator + 'static, K: 'static))]
pub struct ForProps<I: IntoIterator, K> {
    data: I,
    key: Shared<dyn Fn(&I::Item) -> K>,
}

Extensible Props

#[props(extensible(TraitName))] generates a trait exposing the prop struct and its fields. Other prop structs can include an extension field with #[props(extends(TraitName))].

This is useful for component libraries that want prop inheritance or shared prop groups.

#[props(extensible(ViewPropsExt))]
pub struct ViewProps {
    #[props(default)]
    class: String,
}

#[props]
pub struct ButtonProps {
    #[props(extends(ViewPropsExt))]
    view: ViewProps,
    on_click: Option<Shared<dyn Fn()>>,
}

Extension fields are not wrapped in PropValue<T> and cannot also be start or default fields.

build_props!

build_props! drives the generated builder. This is what layout! uses for component prop syntax.

let props = build_props!(ButtonProps(
    .disabled = false,
    .on_click = callback!(|| log::info!("clicked")),
));

Positional arguments fill start fields:

let props = build_props!(TextProps("Hello"));

Named values are converted with prop_value!, so plain values, signals, and existing PropValue<T> values are accepted.

prop_value!

prop_value! converts:

  • plain values to PropValue::from_plain;
  • signals to PropValue::from_signal;
  • existing PropValue<T> values unchanged.
let plain = prop_value!("Hello".to_string());
let reactive = prop_value!(computed!([count] || count.get().to_string()));

Clone this wiki locally