Counter example part:

// ------ ------
//     View
// ------ ------

// (Remove the line below once your `Model` become more complex.)
// `view` describes what to display.
fn view(model: &Model) -> Node<Msg> {
        "This is a counter: ",
        button![model, ev(Ev::Click, |_| Msg::Increment),],
Example from a production app (this website)
pub fn view(base_url: &Url) -> Node<Msg> {
        C![C.mt_32, C.flex, C.justify_center,],
                // sm__
                // lg__
            div![C![C.font_bold,], "404",],
            div![C![C.my_12,], "Page not found"],
                attrs! {
                    At::Href => Urls::new(base_url).home()

  • view is the function that basically transforms Model to HTML.

  • It's invoked by Seed when the app should be rerendered - the typical scenario is:

    1. An action happens - e.g. user clicks on a button.
    2. Seed calls your update function.
    3. Seed schedules rerender.
    4. The browser notifies Seed that it's the right time for the render (see requestAnimationFrame).
    5. Seed calls your view function.
    6. Seed rerenders the app.
  • It's also invoked after the init function to render the app for the first time.

view function signature

fn view(model: &Model) -> Node<Msg>

  • &Model is immutable reference to prevent you from mutating the state directly. It's possible to break this "limitation" with interior mutability but we strongly oppose to do that, because view invocations are pretty unpredictable and such Model changes would be surprising and hard to find. (However there will be a way how to preserve and modify local view variables once React-like Hooks are integrated.)

  • Node represents HTML element or text used as an element content. It's usually created by element macros like div! or span!. (You'll learn about element macros in next chapters.)

  • Your view function can return everything that implements IntoNodes. It means your view signature can be also fn view(model: &Model) -> impl IntoNode<Msg> but we DON'T recommend this form because it's not very expressive and it makes chaining of nested views harder. The most used and preferable forms are -> Node<Msg> or -> Vec<Node<Msg>>.

  • In our Counter example, there is also another Clippy attribute trivially_copy_pass_by_ref. Clippy is sad because the Model in Counter example is too simple so it doesn't make sense to pass it into the view function by reference because copying it would be more efficient. Clippy won't bother you with it once your Model doesn't implement Copy.

How to write a good view

  • Don't hesitate to split view into helpers/"sub-views". If these helpers return nodes, name them view_* - e.g. view_footer or view_menu_item.

  • Root view and all nested view_* functions should have in their function signature as the output value either Node<Msg> or Vec<Node<Msg>>. They can also return corresponding alternatives with Option.

  • Pass only necessary Model variables into view_* functions.

  • When you need to write some helpers, respect the rule "children below the parent" as always.

view is the most complex app part => you'll find information and best practices for things like elements, attributes and event handlers in next chapters.


  • view and view_* functions will have own local state once Seed Hooks are integrated.

  • I'll maybe try to write GUI designer that generates view in the distant future. It should make development more enjoyable & faster and make cooperation between developers and designers smoother. (Don't hesitate to write me your opinions/ideas.)