Fabulous puts an MVU state machine under a declarative F# UI
Declarative UI framework for cross-platform mobile & desktop apps, using MVU and functional programming in F#
At a glance
- What is it?
- Fabulous is an F# framework for cross-platform mobile and desktop apps that separates state and transitions from the view, with a core library and two rendering backends, .NET MAUI and Avalonia. The repository builds core, backends, templates and docs together, and it has just moved everything onto a single 10.0.x version line.
- Who is it for?
- Adopt Fabulous if you write F# and want an application architecture where the state machine is a plain function you can unit test, and you are prepared to accept that the widget tree is a DSL rather than native designer files. Do not adopt it if your team cannot maintain F#, since the whole approach depends on treating the view as a value.
- Can I use it commercially?
- Yes. Apache-2.0 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 4 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 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The view is a value, not a designer file
Fabulous describes itself as declarative, functional applications for .NET using F#, and it combines F#, declarative UI and the Model-View-Update pattern to build mobile and desktop applications with explicit state transitions and testable application logic. The mechanism is that the UI is described as ordinary data. The counter example in the documentation separates a `Model` record, a `Msg` union with Increment and Decrement cases, an `init` function, an `update` function that matches on the message and returns a new model, and a `view` function that returns a widget description built from `Application`, `ContentPage`, `VStack`, `Label` and `Button`. Nothing in that description is a widget instance, so the view cannot be inspected for identity or mutated. The consequence the documentation states is that Fabulous reconciles successive widget descriptions with the native UI, and application state stays ordinary F# data, which makes update functions straightforward to test.
You pick the rendering backend, the core stays the same
The framework is split into an architecture layer and a rendering layer, and the split is explicit. Fabulous provides the application architecture and the declarative DSL, and you choose a UI backend for rendering:.NET MAUI with `Fabulous.MauiControls`, or Avalonia with `Fabulous.Avalonia`. The consequence for a project is that the model, the update function and the view code are portable, while the parts that touch the platform are not. The two backends cover different ground, and the difference matters for the choice: Both backends render the same kind of mobile and desktop application, so the choice comes down to which UI toolkit your platform work already uses. The repository makes the point that core, both backends, extensions, templates, samples, tests, documentation, packaging and CI are maintained together in one place, so a change to the core lands in both backends rather than drifting between them.
An ordinary F# update function is the testable unit
The example is small on purpose, and the reason the framework claims to be testable is visible in it. The update function is `update msg model`, matching on the message union and returning a new model with a record copy. That is a pure function from a message and a model to a model, with no framework types in the signature, so a test is a function call with an assertion on the result. There is no view rendering, no widget tree and no event loop involved in checking that Increment increments. This is the part of the design that would survive a review, and it is what separates this from a framework where the model is only reachable through the UI. The trade is that you write the view description by hand, with all the tedium of a markup language, in exchange for that separation.
The repository is organised by backend, not by feature
The layout is described in the documentation as a set of directories with distinct jobs. `src/neutral/` holds Fabulous.Core, tests and benchmarks, and it is called neutral because it has no platform dependency. `src/maui/` and `src/avalonia/` hold the respective backends and their extensions. `samples/` holds MAUI and Avalonia applications, `templates/` holds the dotnet new template packages, `docs/` holds the authored and API documentation, `website/` is the GitHub Pages site, and `eng/` holds migration and release tooling. Two build files sit at the root, `Directory.Build.props` and `Directory.Packages.props`, which is the central package management pattern, and there is a `global.json` pinning the SDK. The solution is consolidated as `Fabulous.sln`, and the package ID remains `Fabulous` with a core assembly named `Fabulous.Core.dll`.
Building and testing the whole solution
The stated prerequisites are a supported.NET SDK and whatever workloads the platform projects you build require, which is the usual caveat for anything touching MAUI. The two commands that matter are:
dotnet restore Fabulous.sln
dotnet test Fabulous.sln -c ReleaseThe continuous integration story is broader than a compile and a test run, and the list is worth reading as a description of what could break. CI validates formatting, package creation, Avalonia headless UI tests with rendered screenshot artifacts, generated templates, a Windows MAUI sample, WinUI compatibility, and documentation deployment. Screenshot artifacts are the interesting one, because a headless UI test that renders and compares is a direct way to catch a layout regression in a declarative framework, and it explains why a pull request produces artifacts you can look at. Successful pull request runs receive timeline links to their package and screenshot artifacts.
Releases are driven by the changelog file, not by hand
The release process is described in unusual detail, and it reads like a runbook rather than a summary. All packages use the unified `10.0.x` version line and are released from a dedicated workflow. To publish, you move the completed notes from the `Unreleased` section into a new top-level section with the version and a date, add a fresh `Unreleased` section, and push to `main`. Every push to `main` treats the top release section as pending while its matching tag is absent, and after the build and test workflow succeeds, the release workflow publishes the packages, creates the matching tag and creates a GitHub release. A newer push to `main` cancels an in-progress attempt and retries the untagged version from the newer commit, which is a sensible way to avoid publishing a release that was immediately superseded. NuGet publishing uses GitHub OIDC trusted publishing, so no long-lived NuGet API key is stored in the repository.
One version line for the core and both backends
The move to a unified 10.0.x line is the recent structural change, and it is visible in the release history, where 10.0.2, 10.0.3 and 10.0.4 landed within about two weeks in late September and October 2026, and the last push to the main branch was on 2026-10-12. Keeping every package on one version line means the core and the two backends are always released together, which removes the class of bug where a user installs a new core against an old backend. The cost is that a consumer who only wants the Avalonia backend also takes the MAUI backend's release cadence, and that a breaking change in one backend is a breaking change in the whole line. The repository also carries an `AGENTS.md`, a `CHANGELOG.md`, a `CODE_OF_CONDUCT.md` and a `CONTRIBUTING.md`, and the contributing guidance says substantial API changes should be discussed in an issue before implementation, which is a reasonable expectation for a framework that others build on.
Editorial conclusion
Adopt Fabulous if you write F# and want an application architecture where the state machine is a plain function you can unit test, and you are prepared to accept that the widget tree is a DSL rather than native designer files. Do not adopt it if your team cannot maintain F#, since the whole approach depends on treating the view as a value. Verify first which backend you want, since the MAUI and Avalonia packages are separate and the CI runs a Windows MAUI sample, and read the `AGENTS.md` and `CONTRIBUTING.md` files before contributing, since substantial API changes are supposed to be discussed in an issue first.
Frequently asked questions
What is Fabulous?
It is a framework for building cross-platform mobile and desktop applications with F# using a declarative UI and the Model-View-Update pattern, so state and transitions stay separate from the view and the update functions can be tested.
Which UI backends does Fabulous support?
Two. The .NET MAUI backend is `Fabulous.MauiControls` and the Avalonia backend is `Fabulous.Avalonia`. Fabulous itself provides the application architecture and the declarative DSL, and you choose the backend for rendering.
How do I build and test Fabulous?
With a supported .NET SDK and the workloads your platform projects need, run `dotnet restore Fabulous.sln` and then `dotnet test Fabulous.sln -c Release`. The CI also validates formatting, packaging, Avalonia headless UI tests with screenshot artifacts, generated templates and a Windows MAUI sample.
How are Fabulous releases published?
All packages share the 10.0.x version line and are released from a dedicated workflow. You move the completed notes from `Unreleased` into a new dated section in the changelog and push to `main`, after which the release workflow publishes the packages and creates the tag and the GitHub release. NuGet publishing uses GitHub OIDC trusted publishing.
What licence is Fabulous released under?
The Apache License 2.0, with the licence text in the LICENSE.md file at the root of the repository.
Official sources
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.
[](https://hysenlabs.com/projects/fabulous-dev-fabulous)Was this useful?