Skip to main content

Custom Elements & Attributes

FSharp.ViewEngine covers all standard HTML elements and attributes, but you may need custom ones for web components or non-standard attributes.

On this page

Overview

FSharp.ViewEngine covers all standard HTML elements and attributes, but you may need custom ones for web components or non-standard attributes.

Trusted Content Boundaries

Text and attribute values are HTML-encoded by default. The following APIs intentionally cross that safety boundary and must only receive trusted, developer-controlled content:

  • Html.raw and Html.js emit their values without encoding.
  • Inline event-handler helpers such as _onclick contain executable JavaScript. HTML encoding preserves valid markup but does not make untrusted JavaScript safe.
  • Html.el, Html.elVoid, and the name passed to _attr are emitted as markup names without validation.

Keep user-controlled data in normal text nodes and attribute values, where it will be encoded. Do not use user input as raw markup, JavaScript, element names, or attribute names.

Custom Elements

el

Use Html.el to create a custom element with children. This is useful for web components:

open FSharp.ViewEngine
open type Html

el "my-component" {
    _class "container"
    p { "Hello from a web component!" }
}

Renders:

<my-component class="container">
    <p>Hello from a web component!</p>
</my-component>

elVoid

Use Html.elVoid to create a custom self-closing (void) element:

elVoid "my-icon" {
    _attr("name", "star")
    _attr("size", "24")
}

Renders:

<my-icon name="star" size="24">

Nested Web Components

Custom elements can be nested just like regular elements:

el "my-card" {
    _attr("variant", "outlined")
    el "my-card-header" {
        h2 { "Card Title" }
    }
    el "my-card-body" {
        p { "Card content goes here." }
    }
    el "my-card-footer" {
        button { _onclick "handleClick()"; "Action" }
    }
}

Custom Attributes

_attr

Use Html._attr to add any attribute not covered by the built-in helpers.

Key-value attribute

div {
    _attr("my-custom-attr", "value")
    "Content"
}

Renders:

<div my-custom-attr="value">Content</div>

Boolean attribute

Pass only the name to render a valueless (boolean) attribute:

div {
    _attr "my-flag"
    "Content"
}

Renders:

<div my-flag>Content</div>

Combining with Built-in Attributes

Custom attributes work alongside all built-in attributes:

el "sl-button" {
    _attr("variant", "primary")
    _attr("size", "large")
    _attr "pill"
    _onclick "handleClick()"
    _class "my-button"
    "Click Me"
}

Renders:

<sl-button variant="primary" size="large" pill onclick="handleClick()" class="my-button">
    Click Me
</sl-button>

Extending the Html Type

F# supports type extensions which let you add your own elements and attributes to the Html type. This is useful for project-specific conventions or design system components.

Adding Custom Elements

open FSharp.ViewEngine

type Html with
    static member val myCard = TagBuilder("my-card") with get
    static member val myIcon = VoidBuilder("my-icon") with get

Then use them just like built-in elements:

open type Html

myCard {
    _class "shadow-lg"
    h2 { "Title" }
    p { "Card content" }
}

myIcon { _attr("name", "star") }

Adding Custom Attributes

open FSharp.ViewEngine

type Html with
    static member inline _theme (v: string) = { Name = "data-theme"; Value = ValueSome v }
    static member inline _variant (v: string) = { Name = "variant"; Value = ValueSome v }
    static member inline _loading = { Name = "data-loading"; Value = ValueNone }

Then use them alongside built-in attributes:

open type Html

div {
    _theme "dark"
    _variant "outlined"
    _loading
    "Content"
}

Design System Example

You can build a full design system module with reusable elements and attributes:

open FSharp.ViewEngine

type Ds =
    static member val alert = TagBuilder("ds-alert") with get
    static member val badge = TagBuilder("ds-badge") with get
    static member val tooltip = TagBuilder("ds-tooltip") with get
    static member inline _severity (v: string) = { Name = "severity"; Value = ValueSome v }
    static member inline _placement (v: string) = { Name = "placement"; Value = ValueSome v }
    static member inline _dismissible = { Name = "dismissible"; Value = ValueNone }
open type Html
open type Ds

alert {
    _severity "warning"
    _dismissible
    "This is a warning message."
}

tooltip {
    _placement "top"
    button { "Hover me" }
}

Shoelace Example

Here's a more complete example using Shoelace web components:

el "sl-dialog" {
    _attr("label", "Confirm")
    _attr "open"
    p { "Are you sure?" }
    div {
        _slot "footer"
        el "sl-button" {
            _attr("variant", "primary")
            _onclick "this.closest('sl-dialog').hide()"
            "Confirm"
        }
    }
}