Library / SDK
fsprojects/Avalonia.FuncUI avatar
fsprojects/Avalonia.FuncUI

Avalonia FuncUI: a virtual DOM and a view DSL for F# desktop apps

Develop cross-plattform GUI Applications using F# and Avalonia!

1,264 stars83 forksF#MIT

At a glance

What is it?
FuncUI is a thin layer over Avalonia that gives F# developers a virtual DOM, a view DSL and a component model, with an Elmish package for the message-and-update style. Version 2.0 is a port to Avalonia 12, and the DataGrid bindings moved out to their own package.
Who is it for?
FuncUI fits an F# team that wants a single-language desktop application, that likes the model-update-view discipline that Elmish made popular, and that wants the same UI abstraction to work across platforms through Avalonia. It is the wrong choice for a team that has standardised on XAML markup and code-behind, because FuncUI replaces that workflow with a virtual DOM and a DSL, and it is the wrong choice for a mixed-language team, since the ergonomics of the DSL assume F#.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 61 days ago.
What is it written in?
Mainly F#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What FuncUI adds on top of Avalonia

FuncUI is a thin layer built on top of AvaloniaUI, and the README is precise about what that layer contains. It provides abstractions for writing UI applications in different programming styles, and it contains a component and state management system.

The library mostly consists of three things: a virtual DOM, a view DSL and a component model.

The virtual DOM is the layer that makes the rest possible. Avalonia itself has a control tree that is changed imperatively, which is normal for a desktop toolkit, and FuncUI puts a declarative layer on top so that a view can be described as a value and recomputed when the state changes. The DSL is the syntax for that description, and the component model is the way state is held and updated.

The cross-platform part comes from Avalonia rather than from FuncUI, and that is worth separating. FuncUI does not draw anything and does not talk to a windowing system; it produces Avalonia controls. The advantage of the arrangement is that the F# code is the same everywhere and the platform work is Avalonia's problem, and the cost is that a F# developer is working through two layers of abstraction, the DSL on top of the Avalonia control API.

The project is MIT licensed with the text in the `LICENSE` file, the primary language is F#, the default branch is `master`, and the last push was on 2026-08-23, the same day as the v2.0.0 release.

The same counter in two styles

The README shows the same counter twice, once with each style, and the comparison is the clearest introduction to the library.

The component version builds a state value with a hook and reads it inside the view:

fsharp
type Components =
    static member Counter () =
        Component (fun ctx ->
            let state = ctx.useState 0

            DockPanel.create [
                DockPanel.children [
                    Button.create [
                        Button.onClick (fun _ -> state.Set(state.Current + 1))
                        Button.content "click to increment"
                    ]
                ]
            ]
        )

The Elmish version uses a state record, a message union and an update function, and it lives in the separate `Avalonia.FuncUI.Elmish` package:

fsharp
module Counter =
    type CounterState = { count : int }
    let init = { count = 0 }
    type Msg = Increment | Decrement

    let update (msg: Msg) (state: CounterState) : CounterState =
        match msg with
        | Increment -> { state with count =  state.count + 1 }
        | Decrement -> { state with count = state.count - 1 }

The differences are more than stylistic. The component version keeps its state in a hook on the context, so a component is self-contained and the state is created with it. The Elmish version keeps the state outside the view, so the view is a pure function from state to `IView` and the update function is the only thing that changes the state.

That is the classic model-update-view split, and the repository topics list `mvu` alongside `avalonia`, `elmish` and `fsharp`, so the project supports both idioms rather than picking one. The two styles are not the same code with different syntax: a team that has written Elmish will find the Elmish package familiar, and a team that prefers components will find the hook-based version closer to a modern component framework.

The view DSL: create, children and the property setters

Both examples show the same DSL shape, and it is worth reading it carefully because it is the API a developer touches on every control.

`DockPanel.create` takes a list of property setters, and `DockPanel.children` takes the child controls. `Button.create` does the same, with `Button.onClick` for the handler and `Button.content` for the label. `TextBlock` has `TextBlock.dock` and `TextBlock.text`. The nesting is therefore a list literal per control, and the children are given to the parent through a `children` setter.

Two properties of this style are worth naming. First, the result is a value rather than a side effect on a live control, which is what lets the virtual DOM compare the new tree with the old one. Second, the property names are scoped by the control type, `Button.content` rather than a bare `content`, so two controls cannot collide and the compiler can check the setters.

The type safety of the DSL is the other thing the F# typing does for the project, and it is the strongest argument for the library. In a code-behind or in a language without static typing for UI properties, a mistyped property name is a runtime error or a silent no-op. Here, the list of setters is typed per control, so the compiler rejects a property that does not exist on that control.

For the non-Elmish path, the component model adds the `Component` combinator, which takes a function of a context and returns a component, with `ctx.useState` for local state. The context is what a component reads its dependencies from, and it is the counterpart of the Elmish `dispatch`.

The repository keeps the samples in a control catalog, in `src/Avalonia.FuncUI.ControlCatalog`, with a desktop project inside it, which is where the set of available controls is demonstrated.

Version 2.0 is a port to Avalonia 12

The 2.0 release is not a feature release, and the README says so directly.

Version 2.0 of FuncUI is built on Avalonia 12.0, and Avalonia 12 has a number of breaking API changes, which have necessitated matching breaking changes in FuncUI, such as the removal of previously obsoleted public functions and properties. The README points at the official breaking changes documentation for Avalonia 12 for the full details.

So a project moving to FuncUI 2.0 inherits two migrations at once. The Avalonia 12 changes come from the toolkit, and the FuncUI changes come from the layer on top of it. The removal of previously obsoleted members is the part that catches people, because a function that was marked obsolete in an earlier version is now gone, and the warning that would have prepared the code was in the older release notes.

The second change is a dependency cleanup, and it is a good one. The DataGrid bindings that were previously built into the core FuncUI library have moved to the separate `AvaloniaCommunity.FuncUI.Bindings.DataGrid` package. A project that depends on the DataGrid bindings has to reference the new package, and a project that does not use DataGrid no longer carries an unused dependency on it.

That is a pattern worth noting for anyone evaluating the library. Optional surface is being pushed out of the core into community packages, which keeps the main package small and makes the dependency graph of an application depend on what the application actually uses. The cost is that the packages have to be found, and the README links the DataGrid one on NuGet.

Where the examples live, and who maintains them

The repository carries a substantial amount of example code, and the resources are split by purpose.

The getting started guide and the documentation are on the project site at `funcui.avaloniaui.net`, the control catalog is in `src/Avalonia.FuncUI.ControlCatalog` with a desktop project, and the general examples are in `src/Examples`. The component examples have their own folder under `docs/components`, and the Elmish examples are in `src/Examples/Elmish`. The pattern of separate folders for the two styles is consistent, and it is a reasonable signal about which parts of the API are settled in each style.

The maintainer list is explicit. The co-maintainers of Avalonia.FuncUI are @Numpsy, @JordanMarr, @sleepyfran and @JaggerJo, who is named as the project originator. The default maintainer account for projects under `fsprojects` is @fsprojectsgit, the F# Community Project Incubation Space, which handles repository management for the organisation rather than the library itself. So there is a named set of people responsible for the code and an organisation account responsible for the repository, and that division is worth knowing about for a library in a shared namespace.

There is also a commercial support line. Commercial support is provided by Voynoic Systems GmbH, and the README gives a contact address for a commercial support contract or contract work. The address in the README contains a typo, `voyonic-systmes.de` rather than `voyonic-systems.de`, so anyone following it should expect to have to correct the domain.

The last thing the README notes is how to contribute: through issue reports, pull requests, code reviews, documentation and discussion, with no special process required.

Where FuncUI is the wrong tool

The case for FuncUI is an F# shop that wants a cross-platform desktop application and prefers a declarative, statically typed view over mutable control trees.

The case against has three parts.

The first is that it is a layer, and layers cost. Every control property is reached through a FuncUI setter that then has to construct or update an Avalonia control, and the developer is debugging a stack of three things when something does not appear. The README calls itself a thin layer, which is the right word, but a thin layer over a virtual DOM over a control tree is still three layers between the source and the pixels. A team that already has a working XAML and code-behind arrangement for its Avalonia application has no reason to move, and the migration is not small.

The second is the language commitment. The DSL is pleasant in F# because the list-of-setters shape maps onto F# list literals, and in a language with a different shape, or in a mixed-language team, the ergonomics are worse. A JavaScript or C# team that wants Avalonia gets Avalonia, and adding FuncUI means adding F# to the stack.

The third is the size of the community around a specific combination. The F# desktop ecosystem is small, the Elmish part of it is smaller, and a library that sits at the intersection of F#, Avalonia, Elmish and a virtual DOM has a narrow user base. That is not a criticism of the code, it is arithmetic about how many people can hit a bug report, and it matters for a dependency that a business application will live with for years.

The alternative for an F# team that wants a different shape is to use Avalonia's own markup and code-behind, and to keep the Elmish discipline by hand if it wants it. The difference in approach is that FuncUI gives you a virtual DOM, a typed DSL and a component model in exchange for a second abstraction over the toolkit, and the alternative gives you the toolkit and nothing else.

Editorial conclusion

FuncUI fits an F# team that wants a single-language desktop application, that likes the model-update-view discipline that Elmish made popular, and that wants the same UI abstraction to work across platforms through Avalonia. It is the wrong choice for a team that has standardised on XAML markup and code-behind, because FuncUI replaces that workflow with a virtual DOM and a DSL, and it is the wrong choice for a mixed-language team, since the ergonomics of the DSL assume F#. Before adopting it, read the version 2.0 notes, because the port to Avalonia 12 removed previously obsoleted public functions and properties, and check whether you need the `AvaloniaCommunity.FuncUI.Bindings.DataGrid` package, which the core no longer carries.

Frequently asked questions

What is Avalonia FuncUI?

It is a thin layer on top of AvaloniaUI for building cross-platform GUI applications in F#, consisting of a virtual DOM, a view DSL and a component model, together with a component and state management system. The same abstractions let you write in a component style or in the Elmish style.

What changed in FuncUI version 2.0?

Version 2.0 is built on Avalonia 12.0, and Avalonia 12's breaking API changes required matching breaking changes in FuncUI, including the removal of previously obsoleted public functions and properties. The DataGrid bindings also moved out of the core into the separate AvaloniaCommunity.FuncUI.Bindings.DataGrid package.

Can I write FuncUI applications in the Elmish style?

Yes, the Avalonia.FuncUI.Elmish package provides it. The example uses a state record, a message union, an update function and a view returning IView, which is the model-update-view shape, while the component style keeps state in a hook on the component context.

Who maintains Avalonia FuncUI?

The co-maintainers are @Numpsy, @JordanMarr, @sleepyfran and @JaggerJo, who is the project originator, and repository management for projects under the fsprojects namespace is handled by the F# Community Project Incubation Space. Commercial support is offered by Voynoic Systems GmbH.

Official sources

  1. fsprojects/Avalonia.FuncUI on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/fsprojects-avalonia-funcui.svg)](https://hysenlabs.com/projects/fsprojects-avalonia-funcui)

Was this useful?