Skip to main content

Html

Writing Html in Feliz is done through the Html module, which provides a set of functions corresponding to HTML elements.

Hello, Feliz!

This is a simple example of using Feliz to create HTML elements in F#.

Show code
module Example.Html

open Feliz

[<ReactComponent(true)>]
let Example() =
Html.div [
prop.id "main-container"
prop.children [
Html.h1 "Hello, Feliz!"
Html.p "This is a simple example of using Feliz to create HTML elements in F#."
Html.button [
prop.onClick (fun _ -> Browser.Dom.window.alert("Button clicked!"))
prop.text "Submit"
prop.style [
style.color.whiteSmoke
style.backgroundColor.steelBlue
style.borderWidth 0
style.padding (length.em 0.5, length.em 1)
style.display.flex
style.alignItems.center
style.justifyContent.center
style.borderRadius 5
style.cursor.pointer
style.fontWeight.bold
style.letterSpacing (length.em 0.1)
]
]
]
]
warning

Note that you can't mix attributes and child elements. If you use the component overload taking IReactProperty list, you must use prop.children to specify the child elements, as shown above.

However, in the specific case of a single Html.text child, you can also use the prop.text alias. The code above is the same as this:

Overloads

All Html.* functions take a variable number of arguments, they are defined using so called "overloads". Properties are defined in the prop module.

The following example showcases these overloads:

Rightlick and inspect on the example below to see the generated HTML.

Test
Test
Test
Test
Show code
module Example.Overloads

open Feliz

[<ReactComponent(true)>]
let Example() =
Html.div [
Html.div "Test" // overload 1: single child `ReactElement`, also works for float/int
Html.div [ // overload 2: list of properties `IProp list`
prop.id "my-div"
prop.text "Test" // shorthand for `prop.children [ Html.text "Test" ]`
]
Html.div [ // overload 2: list of properties `IProp list`
prop.id "my-div"
prop.children [ // `prop.children` to pass more complex children
Html.text "Test"
]
]
Html.div [ // overload 1: list of children `ReactElement list`
Html.text "Test"
]
]
When to use which?

You can use whichever you prefer. They should offer you the flexibility to write as much or as little boilerplate as you want.

Inline styling

Inline styling can be done using the prop.style property. It takes a list of styles defined in the style module.

This module allows you to write all inline styling in a type-safe way, with autocompletion and compile-time checks.

Test
Show code
module Example.Styling

open Feliz

[<ReactComponent(true)>]
let Example() =
Html.div [
prop.style [
style.display.flex
style.width (length.percent 100)
style.justifyContent.center
style.alignItems.center
style.height 40 // overload for px
style.backgroundColor.dodgerBlue
style.color (color.rgba(255, 255, 255, 0.9))
style.borderRadius 99
style.border (2, borderStyle.solid, color.white)
style.fontWeight.bold
style.fontSize 20 // overload for px
style.cursor.notAllowed
]
prop.text "Test"
]

Examples

Form Inputs

Input elements are dead simple to work with:

Html.input [
prop.className "input"
prop.value state.Crendentials.Password // string
prop.onChange (SetPassword >> dispatch) // (string -> unit)
prop.type'.password
]

Html.input [
prop.className "input checkbox"
prop.value state.RememberMe // boolean
prop.onChange (SetRememberMe >> dispatch) // (bool -> unit)
prop.type'.checkbox
]

Here the onChange property is overloaded with the following types

// generic onChange event
type onChange = Event -> unit
// onChange for textual input boxes
type onChange = string -> unit
// onChange for boolean input boxes, i.e. checkbox
type onChange = bool -> unit
// onChange for single file uploads
type onChange = File -> unit
// onChange for multiple file upload when prop.multiple true
type onChange = File list -> unit
// onChange for input elements where type=date or type=dateTimeLocal
type onChange = DateTime -> unit

The empty element

To render the empty element, i.e. instructing React to render nothing, use Html.none

match state with
| None -> Html.none
| Some data -> render data

Specifying class names

prop.className "button" // => "button"
prop.className [ "btn"; "btn-primary" ] // => "btn btn-primary"

The property className has overloads to combine a list of classes into a single class, convenient when your classes are bound to values so that you do not need to concatenate them yourself.

Enhanced keyboard events

The events prop.onKeyUp, prop.onKeyDown and onKeyPressed are all of type KeyboardEvent -> unit which is correct. The input KeyboardEvent will contain information about the key that was pressed, its char code and whether it was pressed in combination with CTRL or SHIFT (or both). Alongside these default handlers, Feliz provides enhanced handlers that make it even simpler to handle certain key presses. For example, if you have a login form and you want to dispacch Login message when the user hits Enter (very common scenario), you can do it like this:

Html.input [
prop.onKeyUp (key.enter, fun _ -> dispatch Login)
prop.onChange (UsernameChanged >> dispatch)
prop.value state.Username
]

Notice the first properties prop.onKeyUp (key.enter, fun _ -> dispatch Login). It takes two parameters: one is the key we are matching against and another which is of the same type as the default handlers, namely: KeyboardEvent -> unit. This enhanced API also allows you to easily match against combinations of keys such with CTRL and SHIFT as follows:

// Enter only
prop.onKeyUp (key.enter, fun _ -> dispatch Login)
// Enter + CTRL
prop.onKeyUp (key.ctrl(key.enter), fun _ -> dispatch Login)
// Enter + SHIFT
prop.onKeyUp (key.shift(key.enter), fun _ -> dispatch Login)
// Enter + CTRL + SHIFT
prop.onKeyUp (key.ctrlAndShift(key.enter), fun _ -> dispatch Login)

Behind the scenes

Here I explain some of the types and abstractions used in Feliz. This info might help you navigate edge cases or develop your own abstractions on top of Feliz.

IReactProperty

All properties in the prop module implement the IReactProperty interface. The IReactProperty interface is a marker interface, it does not contain any members. It is only used to provide a sensible level of typesafety when working with HTML properties.

Html.input [
prop.className "input"
prop.value state.Crendentials.Password // string
prop.onChange (SetPassword >> dispatch) // (string -> unit)
prop.type'.password
]

The implmentation of this interface is very simple:

type IReactProperty = interface end

As you can see it does nothing but restrict typing. Internally Feliz uses the following function to create the prop.* properties:

module PropHelper =

let inline mkAttr (key: string) (value: obj) : IReactProperty = unbox (key, value)

// ...
[<Erase>]
type props =
// ..
static member inline className (value: string) = PropHelper.mkAttr "className" value

static member inline className (names: seq<string>) = PropHelper.mkAttr "className" (String.concat " " names)

So all prop.* functions create a tuple of key and value and cast it to IReactProperty. This is why you can pass any property in the list of properties of an Html.* function, as long as it implements IReactProperty.

This also means that you can pass your own properties as long as they follow the (key: string, value: obj) tuple shape and implement IReactProperty.


Html.div [
unbox<IReactProperty> ("data-custom-attr", "my-value") // custom attribute
prop.custom("myProp", 42) // custom property using prop.custom
]

tip

The same logic is used for IStyleAttribute and ISvgAttribute