rabbita

    functional Web UI framework for MoonBit

    functional
    html
    UI
    web
    TEA
    Download zip
    Version
    0.14.2
    License
    Apache-2.0
    Last updated
    last month
    Downloads
    79K

    #Rabbita

    A declarative, functional web UI framework inspired by Elm and Bonsai.

    This project was previously named Rabbit-TEA and is now renamed to rabbita .

    #Features

    • Predictable flow

      Each component handles state changes through typed messages. Commands keep side effects explicit.

    • Strict Types

      Rigorous types. No Any sprawl. No stringly-typed APIs.

    • Balanced bundle size

      ~15 KB min+gzip, includes streaming VDOM diff and the MoonBit standard library (DCE via moonc).

    • Modular & Incremental

      Organize reusable UI as ordinary functions that compose state, derived values, and child components. Updates reevaluate dependent callbacks, while equal results stop propagating.

    #Quick Start

    You can try it in the playground or set up a project in the terminal.

    Make sure you have installed moon first:

    moon install moonbit-community/warren warren new my-project cd my-project warren dev

    See Warren for more information.

    #Example

    using @rabbita {type Html, type Val}
    using @html {button, div, h1}

    ///|
    enum Msg {
    Inc
    Dec
    }

    ///|
    fn counter() -> Val[Html] {
    let (count, emit) = @rabbita.create_pure_state(0, update=fn(count, msg) {
    match msg {
    Inc => count + 1
    Dec => count - 1
    }
    })
    count.view(count => {
    div([
    h1(count.to_string()),
    button(on_click=emit(Inc), "+"),
    button(on_click=emit(Dec), "-"),
    ])
    })
    }

    ///|
    fn main {
    @rabbita.new(counter).mount("app")
    }

    #Used By

    Cell

    Deprecated compatibility alias for the former component type.

    New components should return Val[Html] directly.

    Cmd

    A deferred command handled by the Rabbita runtime.

    Cmd models side effects in the update loop. Commands are returned from update or embedded in Html event handlers, and are executed later by the runtime.

    Emit

    Message emitter type used by a state machine.

    Calling emit(msg) returns a Cmd that enqueues msg into the state machine's update loop.

    Enumerate

    Provides stable branch identities for incremental selection.

    Values that represent the same logical branch must return the same tag; distinct branches must return distinct tags.

    Html

    An HTML value produced by the constructors in this package.

    Val

    type Val[A]

    A lazily evaluated value in Rabbita's incremental graph.

    Derived values are recomputed on demand after one of their dependencies changes.

    Val::assoc

    fn[K : Hash + Eq, V : Eq, C : Eq] Val::assoc(a : Val[
    Vector
    [(K, V)]], f : (K, Val[V]) -> Val[C]) -> Val[
    Vector
    [C]]

    Incrementally maps ordered keyed values into a vector.

    Pass a named component to assoc; do not render inline. Keys must be unique; each key owns one branch whose Val tracks updates, and removing it disposes the branch. Output follows vector order, but keys are not attached to Html.

    Example

    fn todo_item(id : Int, title : Val[String]) -> Val[Html] {
    title.view(title => @html.li("\{id}: \{title}"))
    }

    fn todo_list(todos : Val[Vector[(Int, String)]]) -> Val[Html] {
    let rows = todos.assoc(todo_item)
    rows.view(rows => @html.ul(rows))
    }

    Val::assoc_by

    fn[K : Hash + Eq, V : Eq, C : Eq] Val::assoc_by(a : Val[
    Vector
    [V]], f : (K, Val[V]) -> Val[C], by~ : (V) -> K) -> Val[
    Vector
    [C]]

    Incrementally maps values using keys derived by by.

    Derived keys must be unique and stable. Output follows source vector order.

    Val::constant

    fn[A] Val::constant(a : A) -> Val[A]

    Creates an incremental value that always contains a.

    Val::enumerate

    fn[E :
    Enumerate
    + Eq, C : Eq] Val::enumerate(a : Val[E], f : (E) -> Val[C]) -> Val[C]

    Selects and caches an incremental branch for each enumeration tag.

    Immediately match the tag in the enumerate callback and dispatch each case to its own component; do not render inline. Branches are cached with their state and subscriptions. Use Val::switch for disposable branches.

    Example

    priv enum Tab {
    First
    Second
    } derive(Eq)

    impl @rabbita.Enumerate for Tab with fn tag(self) {
    match self {
    First => "first"
    Second => "second"
    }
    }

    fn first_tab() -> Val[Html] {
    Val::constant(@html.h1("First"))
    }

    fn second_tab() -> Val[Html] {
    Val::constant(@html.h1("Second"))
    }

    fn tab_content(tab : Val[Tab]) -> Val[Html] {
    tab.enumerate(tab => {
    match tab {
    First => first_tab()
    Second => second_tab()
    }
    })
    }

    Val::enumerate_by

    fn[E : Eq, C : Eq] Val::enumerate_by(a : Val[E], f : (E) -> Val[C], by~ : (E) -> String) -> Val[C]

    Selects and caches branches using the tag returned by by.

    Values with the same tag reuse the branch created for its first value.

    Val::map

    fn[A : Eq, B] Val::map(a : Val[A], f : (A) -> B) -> Val[B]

    Creates an incremental value by applying f to a.

    The function is reevaluated when the value of a changes.

    Val::map2

    fn[A : Eq, B : Eq, C] Val::map2(a : Val[A], b : Val[B], f : (A, B) -> C) -> Val[C]

    Creates an incremental value derived from two inputs.

    The function is reevaluated when either input value changes.

    Val::map3

    fn[A : Eq, B : Eq, C : Eq, D] Val::map3(a : Val[A], b : Val[B], c : Val[C], f : (A, B, C) -> D) -> Val[D]

    Creates an incremental value derived from three inputs.

    The function is reevaluated when any input value changes.

    Val::map4

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E] Val::map4(a : Val[A], b : Val[B], c : Val[C], d : Val[D], f : (A, B, C, D) -> E) -> Val[E]

    Creates an incremental value derived from four inputs.

    The function is reevaluated when any input value changes.

    Val::map5

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F] Val::map5(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : (A, B, C, D, E) -> F) -> Val[F]

    Creates an incremental value derived from five inputs.

    The function is reevaluated when any input value changes.

    Val::map6

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G] Val::map6(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : Val[F], g : (A, B, C, D, E, F) -> G) -> Val[G]

    Creates an incremental value derived from six inputs.

    The function is reevaluated when any input value changes.

    Val::map7

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H] Val::map7(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : Val[F], g : Val[G], h : (A, B, C, D, E, F, G) -> H) -> Val[H]

    Creates an incremental value derived from seven inputs.

    The function is reevaluated when any input value changes.

    Val::map8

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I] Val::map8(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : Val[F], g : Val[G], h : Val[H], i : (A, B, C, D, E, F, G, H) -> I) -> Val[I]

    Creates an incremental value derived from eight inputs.

    The function is reevaluated when any input value changes.

    Val::map9

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq, J] Val::map9(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : Val[F], g : Val[G], h : Val[H], i : Val[I], j : (A, B, C, D, E, F, G, H, I) -> J) -> Val[J]

    Creates an incremental value derived from nine inputs.

    The function is reevaluated when any input value changes.

    Val::switch

    fn[E :
    Enumerate
    + Eq, C : Eq] Val::switch(a : Val[E], f : (E) -> Val[C]) -> Val[C]

    Selects one incremental branch using the input's enumeration tag.

    Immediately match the tag in the switch callback and dispatch each case to its own component; do not render inline. Changing the tag disposes the active component, so returning to an old tag creates a fresh one.

    Example

    priv enum Page {
    Home
    Settings
    } derive(Eq)

    impl @rabbita.Enumerate for Page with fn tag(self) {
    match self {
    Home => "home"
    Settings => "settings"
    }
    }

    fn home_page() -> Val[Html] {
    Val::constant(@html.h1("Home"))
    }

    fn settings_page() -> Val[Html] {
    Val::constant(@html.h1("Settings"))
    }

    fn page_content(page : Val[Page]) -> Val[Html] {
    page.switch(page => {
    match page {
    Home => home_page()
    Settings => settings_page()
    }
    })
    }

    Val::switch_by

    fn[E : Eq, C : Eq] Val::switch_by(a : Val[E], f : (E) -> Val[C], by~ : (E) -> String) -> Val[C]

    Selects a disposable branch using the tag returned by by.

    Values with the same tag keep the current branch; changing it disposes the branch before creating the next one.

    Val::view

    Creates an HTML view derived from one incremental value.

    The render function is reevaluated when a changes.

    Val::view2

    fn[A : Eq, B : Eq] Val::view2(a : Val[A], b : Val[B], render : (A, B) ->
    Html
    ) -> Val[
    Html
    ]

    Creates an HTML view derived from two incremental values.

    The render function is reevaluated when either input changes.

    Val::view3

    fn[A : Eq, B : Eq, C : Eq] Val::view3(a : Val[A], b : Val[B], c : Val[C], render : (A, B, C) ->
    Html
    ) -> Val[
    Html
    ]

    Creates an HTML view derived from three incremental values.

    The render function is reevaluated when any input changes.

    Val::view4

    fn[A : Eq, B : Eq, C : Eq, D : Eq] Val::view4(a : Val[A], b : Val[B], c : Val[C], d : Val[D], render : (A, B, C, D) ->
    Html
    ) -> Val[
    Html
    ]

    Creates an HTML view derived from four incremental values.

    The render function is reevaluated when any input changes.

    Val::view5

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq] Val::view5(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], render : (A, B, C, D, E) ->
    Html
    ) -> Val[
    Html
    ]

    Creates an HTML view derived from five incremental values.

    The render function is reevaluated when any input changes.

    Val::view6

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq] Val::view6(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : Val[F], render : (A, B, C, D, E, F) ->
    Html
    ) -> Val[
    Html
    ]

    Creates an HTML view derived from six incremental values.

    The render function is reevaluated when any input changes.

    Val::view7

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq] Val::view7(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : Val[F], g : Val[G], render : (A, B, C, D, E, F, G) ->
    Html
    ) -> Val[
    Html
    ]

    Creates an HTML view derived from seven incremental values.

    The render function is reevaluated when any input changes.

    Val::view8

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq] Val::view8(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : Val[F], g : Val[G], h : Val[H], render : (A, B, C, D, E, F, G, H) ->
    Html
    ) -> Val[
    Html
    ]

    Creates an HTML view derived from eight incremental values.

    The render function is reevaluated when any input changes.

    Val::view9

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq] Val::view9(a : Val[A], b : Val[B], c : Val[C], d : Val[D], e : Val[E], f : Val[F], g : Val[G], h : Val[H], i : Val[I], render : (A, B, C, D, E, F, G, H, I) ->
    Html
    ) -> Val[
    Html
    ]

    Creates an HTML view derived from nine incremental values.

    The render function is reevaluated when any input changes.

    batch

    Combine multiple commands into one command.

    cell

    #deprecated("Use `create_state` inside a `() -> Val[Html]` component; return `(Model, Cmd)` from `update`, then map the model to `Html`.")
    fn[Model, Msg] cell(model~ : Model, update~ : (
    Emit
    [Msg], Msg, Model) -> (
    Cmd
    , Model), view~ : (
    Emit
    [Msg], Model) ->
    Html
    , subscriptions? : (
    Emit
    [Msg], Model) ->
    Sub
    ) -> (() -> Val[
    Html
    ])

    Compatibility wrapper for the former effectful Cell constructor.

    New code should call create_state inside a component and map the returned model to Html. The new update callback returns (Model, Cmd) instead of (Cmd, Model).

    cell_with_emit

    #deprecated("Use `create_state` inside a component and pass its returned `Emit` explicitly where it is needed.")
    fn[Model, Msg] cell_with_emit(model~ : Model, update~ : (
    Emit
    [Msg], Msg, Model) -> (
    Cmd
    , Model), view~ : (
    Emit
    [Msg], Model) ->
    Html
    , subscriptions? : (
    Emit
    [Msg], Model) ->
    Sub
    ) -> (
    Emit
    [Msg], () -> Val[
    Html
    ])

    Compatibility wrapper for the former effectful Cell constructor that also exposed its emitter.

    none

    A command that does nothing.

    render_to_string

    #internal(unstable, "Experimental API")
    fn render_to_string(builder : () -> Val[
    Html
    ]) -> String

    Render a component into an HTML string.

    This is the server-side rendering entrypoint for Rabbita. It evaluates the component once and serializes the resulting virtual DOM into static HTML.

    Hydration is currently not supported. If you later call mount on the client, Rabbita will do a fresh client-side render instead of attaching to the existing server-rendered DOM.

    Example

    test "render html to string" {
    inspect(
    @rabbita.render_to_string(() => {
    @rabbita.Val::constant(@html.ul(["todo1", "todo2"].map(x => @html.li(x))))
    }),
    content=(
    #|<ul><li>todo1</li><li>todo2</li></ul>
    ),
    )
    }

    simple_cell

    #deprecated("Use `create_pure_state` inside a `() -> Val[Html]` component, then map the model to `Html`.")
    fn[Model, Msg] simple_cell(model~ : Model, update~ : (Msg, Model) -> Model, view~ : (
    Emit
    [Msg], Model) ->
    Html
    ) -> (() -> Val[
    Html
    ])

    Compatibility wrapper for the former pure Cell constructor.

    simple_cell_with_emit

    #deprecated("Use `create_pure_state` inside a component and pass its returned `Emit` explicitly where it is needed.")
    fn[Model, Msg] simple_cell_with_emit(model~ : Model, update~ : (Msg, Model) -> Model, view~ : (
    Emit
    [Msg], Model) ->
    Html
    , subscriptions? : (
    Emit
    [Msg], Model) ->
    Sub
    ) -> (
    Emit
    [Msg], () -> Val[
    Html
    ])

    Compatibility wrapper for the former pure Cell constructor that also exposed its emitter.

    static_cell

    #deprecated("Use a component returning `Val::constant(html)` instead.")
    fn static_cell(html :
    Html
    ) -> (() -> Val[
    Html
    ])

    Compatibility wrapper for the former static Cell constructor.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io