Lit allows you to render web UIs in a declarative way as React does by using HTML templates. With Fable.Lit you can use F# interpolated strings to build the HTML templates. Also, if you're using Visual Studio Code, we recommend the Highlight F# Templates extension for IDE support of HTML and CSS as embedded languages in F#.

If you check Lit's website you will likely think it focus on Web Components (which are also part of Fable.Lit), but HTML templates by themselves are already a very powerful feature and it's entirely possible to build your web app with them, or even use "virtual" React-like components.

You can use the holes in the interpolated string to add dynamic data to the HTML templates. Normally the holes are only allowed in the attribute value or child node position. When you open the Lit namespace, the html and svg function will be available to declare the templates:

open Lit

[<HookComponent>]
let MyComponent() =
    Hook.useHmr(hmr)
    let value, setValue = Hook.useState "World"

    html $"""
      <div class="content">
        <p>Local state: <i>Hello {value}!</i></p>
        <input
          value={value}
          @keyup={EvVal setValue}>
      </div>
    """

Given that holes in interpolated strings are not typed, is a good idea to wrap your event handler with Ev to make sure you're using the correct type. EvVal is also available to access ev.target.value string in common input's change events.

Use Lit.render to mount the template in a DOM element. If the template was already mounted, it will be updated.

open Browser

let el = document.getElementById("my-container")
MyComponent() |> Lit.render el

Lit uses standard HTML for the templates, with only three special characters @/?/. in some situations:
TypeExample
Event listeners
<button @click={Ev(fun ev -> doSomething())}>Click me!</button>
Boolean attributes
<button ?disabled={not enabled}>Click me!</button>
Properties
<my-component .someData={nonStringData}></my-component>

If you want to learn more about templates, please check Lit's website.

Directives

Lit includes special functions, called "directives" that can control the way the templates are rendered. The Lit class provides the following directives and helpers as static members:

The name and signature of some directives have been adapted from Lit to be more idiomatic in F#.

nothing

Used when you don't want to render anything with Lit, usually in conditional expressions.

html $"""Value: { if value > 0 then value else Lit.nothing } """

classes

Generates a single string from a sequence of classes, or string * bool tuples (false values will be filtered out).

let classes = Lit.classes [
    "button", true
    "is-active", isActive
]

html $"""<button class={classes}>Click me</button>"""

mapUnique

You can pass any iterable (including F# lists) to Lit, but when it's important to identify items in a list (e.g. when the list can be sorted, filtered or included item transitions), use Lit.mapUnique to give each item a unique id.

let getItemTemplate item =
    html $"""<li>{item.name} - {item.price}</li>"""

html $"""
    <ul>
        {items |> Lit.mapUnique
            (fun item -> item.id)
            getItemTemplate}
    </ul>
    """

ifSome

Sets the attribute if the value is Some and removes the attribute if the value is None.

html $"""<img src="/images/${ifSome filename}">"""

onChange

Prevents re-render of a template function unless one of the dependencies has changed.

let greetingTemplate (name: string) (age: int) =
    html $"Hey {name}! You are {age} years old!"

Lit.onChange("Angel", 10, greetingTemplate)

ofPromise

Shows the placeholder until the promise is resolved.

let deferredTemplate = promise {
    do! Promise.sleep 80
    return html $"<p>Sorry for being late!</p>"
}

Lit.ofPromise(deferredTemplate, placeholder=html $"<p>I'm already here!</p>")

ofImport

Lazily imports a register or render function from another module, this helps JS bundlers to split the code and optimize loading times. Be careful not to reference anything else from the imported module.

// Components.fs

module Components

[<HookComponent>]
let MyComponent(text: string) = // ..

// App.fs

Lit.ofImport(Components.MyComponent, fun render -> render "something")

You can access to the raw bindings via the LitBindings static class. Check the full list in the lit docs

Elmish

Fable.Lit.Elmish allows you to write a frontend app using the popular Elmish library by Eugene Tolmachev with a view function returning Lit.TemplateResult. The package also includes support for Webpack's Hot Module Replacement out-of-the-box thanks to Maxime Mangel original work with Elmish.HMR.

open Elmish
open Lit

type Model = ..
type Msg = ..

let init() = ..
let update msg model = ..
let view model dispatch = ..

open Lit.Elmish

Program.mkProgram initialState update view
|> Program.withLit "app-container"
|> Program.run