Writing a widget crate
fenestra widgets are plain functions returning Elements — publishing
a widget crate needs no traits to implement, no macros, no registration.
fenestra-charts is the
reference: read its source next to this checklist.
The contract
-
Depend on
fenestra-coreonly. Widgets are pure tree builders; they never need the runner. Pullfenestra-shellin as a dev-dependency for golden tests.[dependencies] fenestra-core = "0.10" [dev-dependencies] fenestra-shell = "0.7" -
Take colors from the theme, never hardcode. Style through
.themed(|t, s| s.bg(t.elevated_surface(1)).border(1.0, t.border_subtle))— your widget then works in light, dark, and every generated theme, including ones that don’t exist yet. -
Stay Elm-pure. Widgets own no state: take the current value and emit messages (
on_pick(impl Fn(..) -> Msg)); the app stores. If your widget seems to need internal state, it needs a value + handler pair instead. -
Simple widgets are functions, configurable ones are builders.
sparkline(values) -> Element<Msg>for one-liners; a struct with methods +impl From<W<Msg>> for Element<Msg>once there are options (look atcomboboxordata_tablein the kit). -
Name things for queries. Set
.semantics(..)and.label(..)on every meaningful node — that’s what makesby::role(..).name(..)find your widget in users’ tests, and what screen readers announce. Give stateful nodes stable.id(..)s. -
No panics, ever. Hostile input (empty lists, NaN, negative sizes) renders something sane. Test it:
let image = render_element(sparkline([f32::NAN]), &theme, (200, 50)); -
Golden-test the look. One snapshot per widget family:
assert_png_snapshot(snapshot_dir(), "charts", &image);Goldens travel with the crate; users see exactly what they get, and your CI catches visual regressions on the reference platform.
-
Drive behavior through the harness, not coordinates:
let mut h = Harness::new(app, Theme::light(), (400, 300)); h.click(&by::role(Semantics::Button).name("sort by name"));
Versioning
Track fenestra’s minor version (fenestra-core = "0.10") and re-test on
each release; the core IR and builder vocabulary are the stable
surface. After 1.0, semver does the rest.