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)
]
]
]
]
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.
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"
]
]
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.
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
]
The same logic is used for IStyleAttribute
and ISvgAttribute