Packages

Firelight

The core package: F# bindings for Lit. Define web components with reactive properties, scoped styles and html templates.

Why use it

Web components are the browser's own component model. A custom element works in any page, alongside any framework or none. Lit takes away the boilerplate: you declare properties and return a template, and Lit updates only the parts of the page that changed.

Firelight lets you write those components in F#. Templates are F# interpolated strings, so the values and event handlers in them are ordinary typed F# code. The bindings themselves compile away: your component imports Lit directly, and there is no Firelight runtime to ship.

Example

A star rating. value is a reactive property that also reads and writes the value attribute, the styles are scoped to the component, and choosing a star raises a rating-changed event. The message below comes from plain JavaScript on this page listening for that event.

module Snippets.Rating

open Fable.Core
open Fable.Core.JsInterop
open Browser.Types
open Firelight
open type Firelight.Lit

// Each star is a button named "n out of 5"; the one matching the rating is pressed.
let private star (n: int) (value: int) (select: unit -> unit) =
    html
        $"""<button class={if n <= value then "on" else ""} aria-label="{n} out of 5"
                aria-pressed={n = value} @click={fun _ -> select ()}>{if n <= value then "★" else "☆"}</button>"""

/// <my-rating value="3"></my-rating>
/// Clicking a star sets `value` and raises a "rating-changed" event for the page to handle.
[<AttachMembers>]
type Rating() =
    inherit LitElement()

    static member properties =
        PropertyDeclarations.create [
            "value", PropertyDeclaration<int>(reflect = true)
        ]

    static member styles =
        css
            $$"""
        button { all: unset; cursor: pointer; font-size: 2rem; color: var(--muted); border-radius: 0.25rem; }
        button.on { color: var(--accent); }
        button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
        """

    member val value = 0 with get, set

    member this.Select(stars: int) =
        this.value <- stars
        this.dispatch (Event.customEvent ("rating-changed", stars))

    override this.render() =
        html $"""{[ for n in 1..5 -> star n this.value (fun () -> this.Select n) ]}"""

defineElement<Rating> "my-rating"

Pick a rating.

Install

dotnet add package Firelight
npm install lit

New to Firelight? The Get started tutorial creates an app from a template, then gives its component an attribute, an event, styles and a list.