Firelight.Task
0.3.0
dotnet add package Firelight.Task --version 0.3.0
NuGet\Install-Package Firelight.Task -Version 0.3.0
<PackageReference Include="Firelight.Task" Version="0.3.0" />
<PackageVersion Include="Firelight.Task" Version="0.3.0" />
<PackageReference Include="Firelight.Task" />
paket add Firelight.Task --version 0.3.0
#r "nuget: Firelight.Task, 0.3.0"
#:package Firelight.Task@0.3.0
#addin nuget:?package=Firelight.Task&version=0.3.0
#tool nuget:?package=Firelight.Task&version=0.3.0
Firelight
Web Components for F#. Build reactive, standards-based UI components using Lit and Fable.
Firelight gives you idiomatic F# bindings to Lit's lightweight Web Components platform - type-safe reactive properties, composable templates, the Elmish MVU loop, and cross-component context - all compiling to lean, standards-compliant JavaScript.
Packages
| Package | Description |
|---|---|
Firelight |
Core bindings: LitElement, html/css templates, directives, reactive properties |
Firelight.Context |
Context protocol for sharing state across component trees without prop drilling |
Firelight.Elmish |
Elmish (MVU) integration via reactive controllers |
Firelight.Observers |
Reactive controllers for browser mutation, intersection, resize, and performance observers |
Firelight.Router |
Client-side routing via the URL Pattern API |
Firelight.Signals |
Bindings to Lit Labs signals for shared reactive state and targeted template updates |
Firelight.Motion |
Bindings to Lit Labs animations and spring controllers |
Firelight.Task |
Bindings to Lit's @lit/task reactive controller for async work |
Firelight.Virtualizer |
Bindings to Lit Labs viewport virtualization for large lists |
Quick Start
Define a component by inheriting LitElement, declare reactive properties, and implement render:
open Fable.Core
open Firelight
open type Firelight.Lit
[<AttachMembers>]
type Counter() =
inherit LitElement()
static member properties =
PropertyDeclarations.create [ "count", PropertyDeclaration<int>() ]
member val count = 0 with get, set
override this.render() =
html $"""
<p>Count: {this.count}</p>
<button @click={fun _ -> this.count <- this.count + 1}>
Increment
</button>
"""
defineElement<Counter> "my-counter"
Use it anywhere in HTML:
<my-counter></my-counter>
Elmish Integration
For components with non-trivial state, Firelight.Elmish wires an Elmish Program directly to the component lifecycle:
open Fable.Core
open Firelight
open Firelight.Elmish
open type Firelight.Lit
type Msg = Increment | Decrement
let init () = 0
let update msg model =
match msg with
| Increment -> model + 1
| Decrement -> model - 1
[<AttachMembers>]
type Counter() as this =
inherit LitElement()
let elmish = ElmishController.simple this init update
override _.render() =
let model = elmish.model
html $"""
<button @click={fun _ -> elmish.dispatch Decrement}>-</button>
<span>{model}</span>
<button @click={fun _ -> elmish.dispatch Increment}>+</button>
"""
defineElement<Counter> "my-counter"
Context
Firelight.Context lets you broadcast state (like an Elmish dispatch function) to any descendant component, regardless of nesting depth:
open Fable.Core
open Fable.Core.JsInterop
open Firelight
open Firelight.Context
open type Firelight.Lit
type Msg = Increment | Decrement
// Define a typed context: a symbol branded with the type of value it carries
type DispatchContext =
inherit Context<Msg -> unit>
inherit symbol
let dispatchContext: DispatchContext = LitContext.createContext (JS.Symbol())
// Provide it from a parent component
[<AttachMembers>]
type App() =
inherit LitElement()
let dispatch (msg: Msg) = JS.console.log msg
let provider = ContextProvider(jsThis, ContextProvider.Options(dispatchContext, dispatch))
override _.render() = html $"<increment-button></increment-button>"
// Consume it in any descendant
[<AttachMembers>]
type IncrementButton() =
inherit LitElement()
let consumer = ContextConsumer(jsThis, ContextConsumer.Options(dispatchContext))
override _.render() =
html $"""<button @click={fun _ -> consumer.value |> Option.iter (fun dispatch -> dispatch Increment)}>+</button>"""
Router
Firelight.Router provides client-side routing built on the browser's URL Pattern API. Define routes as URL patterns with typed extractors, and use RouterController to wire routing into Lit's reactive lifecycle:
open Fable.Core
open Browser.Types.URLPattern
open Firelight
open Firelight.Router
open type Firelight.Lit
type Page = Home | About | User of id: string | NotFound
let matchUser (result: URLPatternResult) =
match result.pathname.groups.["id"] with
| Some id -> User id
| None -> NotFound
[<AttachMembers>]
type MyApp() as this =
inherit LitElement()
let router =
[ "/", (fun _ -> Home)
"/about", (fun _ -> About)
"/users/:id", matchUser ]
|> createRouter NotFound
let routing = RouterController(this, router)
override _.render() =
match routing.route with
| Home -> html $"<h1>Home</h1>"
| About -> html $"<h1>About</h1>"
| User id -> html $"<h1>User {id}</h1>"
| NotFound -> html $"<h1>Not Found</h1>"
The RouterController handles popstate events, intercepts clicks on links that match one of its routes (plus hash links, with smooth scrolling), and manages history.pushState navigation automatically. A urlpattern-polyfill npm dependency is included for browsers without native support.
Async Tasks
Firelight.Task binds Lit's @lit/task controller. The F# type is named LitTask to keep it distinct from System.Threading.Tasks.Task. Pass an argument array to run automatically when its values change, or set autoRun = !^false and call run() yourself.
open Fable.Core
open Fable.Core.JsInterop
open Firelight
open Firelight.Task
// Stands in for a real request, e.g. a fetch to your API. promise { } is Fable.Promise's.
let fetchProduct (id: string) : JS.Promise<string> =
promise {
do! Promise.sleep 500
return $"Product {id}"
}
[<AttachMembers>]
type ProductView() as this =
inherit LitElement()
let mutable productId = "123"
let loadProduct (args: string[]) (_: TaskFunctionOptions) : TaskResult<string> =
match args with
| [| id |] -> !^(fetchProduct id)
| _ -> initialState // args always holds one id
let product =
LitTask(
this,
TaskConfig(
TaskFunction(loadProduct),
args = (fun () -> [| productId |])
)
)
override _.render() =
product.render(
StatusRenderer(
pending = (fun () -> Lit.html $"<p>Loading...</p>"),
complete = (fun name -> Lit.html $"<p>{name}</p>"),
error = (fun error -> Lit.html $"<p>{error}</p>")
)
)
|> unbox
The task exposes status, value, error, and taskComplete. Its task function receives an AbortSignal through TaskFunctionOptions, aborted when a newer run supersedes the current one. Its type is Fable.Fetch's, which Firelight.Task depends on, so it passes straight to Fable.Fetch's fetch: fetch url [ Signal options.signal ].
Signals
Firelight.Signals binds @lit-labs/signals. Use LitSignals.defineElement to register a component with SignalWatcher. Reading a signal with get() during a render then schedules an update when its value changes. watch updates only its template part; the signal-aware html and svg tags apply watch to interpolated signals automatically.
open Firelight
open Firelight.Signals
open type LitSignals
let count = signal 0
let doubled = computed (fun () -> count.get() * 2)
type SignalCounter() =
inherit LitElement()
override _.render() =
html $"""
<p>Double: {doubled}</p>
<button @click={fun _ -> count.set(count.get() + 1)}>Increment</button>
"""
defineElement<SignalCounter> "signal-counter"
open type LitSignals brings its signal-aware html and svg tags into scope. If you also use open type Firelight.Lit, open LitSignals afterward: its html and svg shadow Lit's tags. Call Lit.html explicitly when you want the regular tag, and use watch count inside it for a targeted update. updateEffect attaches an effect to an element registered with defineElement and returns a dispose function. This Lit Labs package and its signal polyfill are experimental; pin compatible versions when deploying it.
Motion
Firelight.Motion binds @lit-labs/motion. Use Motion.animate() in an element expression to animate layout changes between renders. MotionOptions supports timing, entry and exit keyframes, guards, IDs for transitions between elements, and callbacks. The package also exposes AnimateController, SpringController, and SpringController2D.
open Fable.Core
open Fable.Core.JsInterop
open Firelight
open Firelight.Motion
[<AttachMembers>]
type MovingBox() =
inherit LitElement()
let mutable shifted = false
override this.render() =
Lit.html $"""
<button @click={fun _ -> shifted <- not shifted; this.requestUpdate()}>Move</button>
<div class={if shifted then "shifted" else ""}
{Motion.animate(MotionOptions(keyframeOptions = MotionKeyframeOptions(duration = !^300.0), ``in`` = Motion.fade))}>
</div>
"""
MotionKeyframe.create accepts arbitrary CSS properties for custom keyframes. The animate directive needs a Lit element expression, and its host must be a LitElement.
Documentation
- Official Lit Documentation - for a complete API reference
- Components and Templates - architectural guide covering component patterns, template composition, and communication strategies
- Styling Components - getting global CSS (Tailwind, design systems, icon fonts) into shadow roots via constructed stylesheets
- Elmish DevTools - persisting Elmish state to
localStoragefor better HMR development experience - Sample Projects - annotated examples from a basic tutorial to a full drag-and-drop Kanban board and multi-page routing
- Observers and Virtualizer sample - resize observation and two ways to virtualize a large list
Getting Started
See the GettingStarted sample for a guided walkthrough covering reactive properties, styles, events, controllers, context, and Elmish - each concept in its own focused module.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Fable.Browser.Dom (>= 2.20.0)
- Fable.Core (>= 5.0.0)
- Fable.Fetch (>= 2.7.0)
- Firelight (>= 0.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0 | 52 | 10/4/2026 |