Skip to content

Elements and attributes

An element takes one list holding everything: children, attributes and event handlers, in any order.

Html.article
    [
        attr.className "card"
        Html.h2 "Title"
        Html.p "Body text."
    ]

Everything on this page follows one rule: a value is static, a function is reactive, and a signal passed directly is reactive too.

Text

Text-bearing elements are overloaded. A string is static. A Var or Signal passed directly updates with it. Html.text takes a function for text computed from several signals:

open Fable.Ripple
open Fable.Ripple.Dom

let app () =
    let count = Var.create 0

    Html.div
        [
            Html.button
                [
                    on.click (fun _ -> count.Value <- count.Value + 1)
                    Html.text "Count"
                ]

            Html.p "A static line."
            Html.p [ Html.text (fun () -> $"A computed line: count is %d{count.Value}.") ]
            Html.p count
        ]

Html.mount "app" (app ())

Attributes

The same three forms apply to attributes:

open Fable.Ripple
open Fable.Ripple.Dom

let app () =
    let enabled = Var.create false

    Html.div
        [
            Html.button
                [
                    on.click (fun _ -> enabled.Value <- not enabled.Value)
                    Html.text "Toggle"
                ]
            Html.p
                [
                    attr.style (fun () ->
                        if enabled.Value then
                            "color: seagreen"
                        else
                            "color: gray"
                    )
                    Html.text (fun () -> if enabled.Value then "enabled" else "disabled")
                ]
        ]

Html.mount "app" (app ())

Classes

Beyond attr.className, three helpers cover the common shapes:

  • attr.classes [ "card"; "wide" ] - a list joined with spaces.
  • attr.classList [ "card", true; "active", false ] - names paired with conditions. A function form re-evaluates them.
  • attr.toggleClass ("active", fun () -> isActive.Value) - one name, one reactive condition.

className and classes own the attribute and write it whole. classList and toggleClass add and remove only their own names, so a static base class and a reactive toggle sit on the same element:

open Fable.Ripple
open Fable.Ripple.Dom

let app () =
    let selected = Var.create false

    Html.div
        [
            Html.button
                [
                    on.click (fun _ -> selected.Value <- not selected.Value)
                    Html.text "Select"
                ]

            Html.p
                [
                    attr.className "card"
                    attr.toggleClass ("active", selected)
                    Html.text "card stays; active comes and goes"
                ]
        ]

Html.mount "app" (app ())

Form properties

attr.value, attr.checked', attr.disabled, attr.hidden, attr.selected, attr.readOnly, attr.required, attr.multiple and attr.isOpen set DOM properties rather than attributes - what a running page reads:

open Fable.Ripple
open Fable.Ripple.Dom

let app () =
    let saving = Var.create false

    Html.div
        [
            Html.label
                [
                    Html.input
                        [
                            attr.type' "checkbox"
                            attr.bindChecked saving
                        ]
                    Html.text "saving"
                ]

            Html.button
                [
                    attr.disabled saving
                    Html.text "Save"
                ]
        ]

Html.mount "app" (app ())

Custom attributes

attr.custom sets an attribute the DSL has no name for:

attr.custom ("aria-current", fun () -> if active.Value then "true" else "false")

It calls setAttribute, so it writes the markup rather than the live property. For a name that is a form property, use the named member above.

Refs

attr.ref hands you the raw HTMLElement - to attach an observer, mount a third-party widget, or read a measurement:

Html.div [ attr.ref (fun el -> el.textContent <- "reached directly") ]

It runs while the element is being built, inside the enclosing scope, so a Signal.onCleanup registered in it tears down with the element.

The element is not in the document yet. focus() does nothing, getBoundingClientRect() returns zeros, and dialog.showModal() throws. Defer those to an event or a timeout.

SVG

The Svg type mirrors Html for elements in the SVG namespace, with svgAttr for their attributes. See SVG.

Edit this page