API tour
The examples below are API fragments showing common controls and UI patterns. They are not
complete apps, and no screenshot-to-code attribution is established for them. Use the
beginner journey and
technical recipes for designated compiled code. They use day::prelude::* for the common APIs. For the underlying
concepts, see Pieces, Reactivity, and Layout.
The UI snippets use generated res::str accessors from an app’s Fluent catalog; see the
example messages and catalog setup.
A first app
launch takes window options and a root closure that returns a Piece. Entry points and loop
ownership vary by backend; mobile/web hosts can own the loop. The desktop fragment below needs
a manifest, build script, and resource catalog as shown in the first-app tutorial.
use day::prelude::*;
day::resources!();
fn main() {
day::launch(res::window_options().size(480.0, 640.0), root);
}
fn root() -> impl Piece {
label(res::str::hello_native_world()).padding(Space::XL)
}
Signals: state that binds
A Signal<T> is a Copy reactive cell; copying copies a handle to one shared slot, not the
value, so the same signal can live in as many closures as you like. Reading inside
a bound closure subscribes it; writing re-runs exactly the closures that read it
(Reactivity explains the model).
let count = Signal::new(0i64);
count.get(); // read (tracks the caller as a dependency)
count.set(5); // replace
count.update(|c| *c += 1); // mutate in place
count.with(|c| c.abs()); // borrow without cloning
count.get_untracked(); // read without creating a dependency
A closure passed to a reactive property, such as a label’s text, tracks its signal reads. Changing the signal reruns that binding. An ordinary closure or event handler does not become reactive just because it reads a signal.
// The message reads the signal and follows both count and locale changes.
label(res::str::click_count(count))
Text, buttons, and layout
(Layout is the full model behind the containers here.)
Pieces compose with plain function calls; containers take a tuple of children and expose builder methods for spacing, padding, and alignment.
column((
label(res::str::counter()).font(Font::Title),
row((
button(res::str::decrement())
.action(move || count.update(|c| *c -= 1))
.id(id!(decrement_button)),
label(res::str::counter_value(count)).id(id!(counter_label)),
button(res::str::increment())
.action(move || count.update(|c| *c += 1))
.id(id!(increment_button)),
))
.spacing(Space::S),
divider(),
spacer(),
))
.spacing(Space::M)
.align(HAlign::Leading)
.padding(Space::L)
Wrap any subtree in scroll(...) to make it scroll natively.
Inputs
(Each input is a piece; Pieces covers the vocabulary.)
Editable controls take a signal directly. User input changes the signal, and changes from your code update the control.
let name = Signal::new(String::new());
let volume = Signal::new(40.0);
let subscribed = Signal::new(false);
#[derive(Clone, Copy, PartialEq)]
enum Size {
S,
M,
L,
}
let size = Signal::new(Size::M);
column((
labeled(
res::str::name(),
text_field(name)
.placeholder(res::str::your_name())
.id(id!(name_field)),
),
labeled(
res::str::volume(),
slider(volume).range(0.0..=100.0).id(id!(volume_slider)),
),
labeled(
res::str::subscribe(),
toggle(subscribed).id(id!(subscribe_toggle)),
),
picker(
size,
[
(Size::S, res::str::small()),
(Size::M, res::str::medium()),
(Size::L, res::str::large()),
],
)
.segmented()
.a11y(|a| a.label(res::str::size()))
.id(id!(size_picker)),
))
picker is one-of-N over a typed value (size is a Signal<Size> here, any Clone + PartialEq
type works) with three native stylings (.menu(), .segmented(), .inline());
text_area is the multi-line counterpart of text_field.
Keyboard focus is a binding too: .focused(editing) ties a control to a Signal<bool>, or
.focused((field, Field::Name)) binds one control of a form sharing a Signal<Option<Field>>:
native focus changes write the signal, writing the signal moves focus (and None dismisses the
soft keyboard on mobile). text_field(...).on_submit(...) handles the Return key, so chaining
fields is one signal write. The focus reference has the rules and the
per-platform map.
Conditionals and collections
when shows a subtree while a condition holds; it is itself reactive. Chain .otherwise for the
else arm.
when(
move || !name.with(|s| s.is_empty()),
move || label(res::str::greeting(name)),
)
.otherwise(|| label(res::str::tell_me_your_name()))
Keyed collections (each) build one child per item and reconcile by key when the list changes,
so each row keeps its own state across updates.
Progress and canvas
progress takes a fraction (a value or a reactive closure); spinner is indeterminate. canvas
hands you a native 2D drawing surface; Day never rasterizes it itself.
progress(move || volume.get() / 100.0); // determinate, tracks the slider live
spinner(); // indeterminate
canvas(move |d, size| {
let r = Rect::from_size(size).inset(8.0);
d.stroke(Shape::Arc { rect: r, start_deg: 135.0, sweep_deg: 270.0 },
Color::rgba(0.5, 0.5, 0.55, 0.35), 6.0);
let frac = (volume.get() / 100.0).clamp(0.0, 1.0);
d.stroke(Shape::Arc { rect: r, start_deg: 135.0, sweep_deg: 270.0 * frac },
Color::hex(0x2F6FDE), 6.0);
})
Navigation
(The whole model, with per-platform mappings: Navigation.)
Navigation state lives in signals too. Changing a selection or path updates the native container; using its tabs or back button updates the signal. Choose between two containers:
nav is a one-of-N choice bound to a Signal<String>. Its .style picks the native
chrome: Sidebar becomes a NavigationSplitView (an AdwNavigationSplitView on GTK, an
NSSplitView source list on macOS, a pushing list on mobile); Tabs becomes a native tab widget.
let section = Signal::new(String::new());
nav(section)
.style(NavStyle::Sidebar)
.title(res::str::my_app())
.header(sidebar_header)
.item("home", res::str::home(), home_page)
.item("settings", res::str::settings(), settings_page)
nav_stack is a push/pop stack bound to a Signal<Vec<String>> path. Day reconciles the
native stack (UINavigationController, AdwNavigationView, the Android back stack) to the path.
let path = Signal::new(Vec::<String>::new());
nav_stack(path, home_view).destination(|key| detail_view(key))
// push: path.update(|p| p.push("item-42".into()));
// the native back button writes the pop back into `path`.
Each navigation surface uses a separate signal. A Tabs [nav host](/docs/reference/glossary#nav host) or a nav_stack
inside a Sidebar nav host can be nested without additional state synchronization. Keys don’t have to be strings: declare a
day::routes! { enum Section { Home => "home", … } } enum (or implement Route by hand for
keys that carry data, like Item { id: u32 } ↔ "item-42") and bind the nav host to
Signal<Option<Section>> and the stack to Signal<Vec<Item>>. It’s the same API, compile-checked
(navigation guide).
Deep links and dayscript
A string-route adapter maps those signals to routes, so keys also serve as routes:
navigate("settings"); // select the settings section / tab
nav_back(); // pop the innermost surface
current_route(); // the full path, outermost surface first
The same keys drive deep links (DAY_DEEPLINK=settings;
deep links reference) and dayscript automation
(navigate: { route: settings }; testing with dayscript).
Localization and accessibility
Text localizes through Fluent with tr, including interpolated signal arguments. Every Piece can
carry accessibility metadata.
label(res::str::greeting(name));
progress(move || volume.get() / 100.0)
.a11y(|a| a.role(Role::Meter).label(res::str::volume_level()));
Ids and testing
Give any Piece a stable .id(id!(name)) and dayscript can find, drive, and assert it, using the same
script on every platform.
button(res::str::increment())
.action(move || count.update(|c| *c += 1))
.id(id!(increment_button))
Extending with Day Pieces
A native component you write (or install) plugs in like a built-in. The showcase’s flavor
picker is an external combo_box Piece from a separate crate, with free-form text entry plus a
native dropdown, both bound to signals:
use day_piece_combobox::combo_box;
let flavors = Signal::new(vec!["vanilla".into(), "chocolate".into()]);
let flavor = Signal::new(String::new()); // the typed-or-picked text
combo_box(flavors, flavor).id(id!(flavor_combo))
Day Pieces ship as ordinary Rust crates. The extension model explains the
tiers, from pure composition to per-toolkit native code. On macOS and iOS the same mechanism
hosts your own SwiftUI: declare a local Swift package and call the generated typed constructor
(crate::swiftui::MyView(title, count)) like any other piece
(SwiftUI embedding).
Next: Pieces for the model behind all of this, or the CLI & projects that build, launch, and script it.