Lit.Server.Unofficial 2.21.3

dotnet add package Lit.Server.Unofficial --version 2.21.3
                    
NuGet\Install-Package Lit.Server.Unofficial -Version 2.21.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Lit.Server.Unofficial" Version="2.21.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lit.Server.Unofficial" Version="2.21.3" />
                    
Directory.Packages.props
<PackageReference Include="Lit.Server.Unofficial" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Lit.Server.Unofficial --version 2.21.3
                    
#r "nuget: Lit.Server.Unofficial, 2.21.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Lit.Server.Unofficial@2.21.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Lit.Server.Unofficial&version=2.21.3
                    
Install as a Cake Addin
#tool nuget:?package=Lit.Server.Unofficial&version=2.21.3
                    
Install as a Cake Tool

Lit.Server.Unofficial

Render Fable.Lit templates to HTML on .NET — no Node, no @lit-labs/ssr.

dotnet add package Lit.Server.Unofficial

The same html $"..." view compiles to lit in the browser and to HTML on the server, because both sides resolve open Lit — one against this package, the other against Fable.Lit.Unofficial. No conditional compilation, no template written twice.

open Lit
open Lit.Server

let counter, _ = Views.Counter.init ()

// Plain HTML.
Server.render (Views.Counter.view counter ignore)

// HTML carrying lit's hydration markers, so the browser can adopt it
// with Hydrate.adopt or Program.withLitHydrated.
Server.renderHydratable (Views.Counter.view counter ignore)

Server.toNode and Server.toHydratableNode return a node rather than a string, for composing into HtmlTypeProvider templates without anything being escaped twice. Server.toShadowRootNode emits <template shadowrootmode="open"> with styles inside, which the parser attaches as a shadow root while it reads the page.

Event handlers are dropped on the server — a closure cannot be serialised — and become real listeners the moment lit adopts the markup.

A component's shadow root

A LitElement can arrive rendered as well. Keep what it draws where both sides reach it, write that into the component's own tag, and switch adoption on in the browser:

// Shared: compiled by both.
module Badge =
    let styles = ":host { display: block }"

    let view (count: int) (bump: unit -> unit) =
        html $"""<button @click={Ev(fun _ -> bump ())}>{count}</button>"""

// Server: what goes inside <my-badge></my-badge> in the page.
Server.toShadowRootNode Badge.styles (Badge.view 0 ignore)

// Browser: once, at startup.
Hydrate.elements ()

[<LitElement("my-badge")>]
let MyBadge () =
    LitElement.init (fun config -> config.styles <- [ Lit.unsafeCSS Badge.styles ]) |> ignore
    let count, setCount = Hook.useState 0
    Badge.view count (fun () -> setCount (count + 1))

The component must render, first time, what the server rendered: the same view from the same data. Without Hydrate.elements () it draws a second copy beside the server's, and says so in the console.

Inside a view

toShadowRootNode fills a hole in a page template. A component that sits inside an island's view, or inside another component's, has a view around it instead, and Lit.shadowRoot is the same thing where a view can reach it:

// Added to the Badge module above: what the server draws for a badge inside a view.
let drawn (count: int) = Lit.shadowRoot styles (view count ignore)

// A view with a badge in it.
let view model dispatch =
    html $"""<section>
               <my-badge>{Badge.drawn model.Count}</my-badge>
               <button @click={Ev(fun _ -> dispatch Reset)}>reset</button>
             </section>"""

On the server it writes the component's shadow root into its tag. In the browser it is Lit.nothing, because there a component draws its own root. It has to come first inside the element, which is the element the parser attaches the root to.

The styles and the view are paired once, in the component's own module, rather than in each view that uses it. Lit.Server does not work out a component's stylesheet, so that pairing is the one place the two are kept from disagreeing. If they do disagree, a development build of the component says so in the console.

If the view hands the component a property (.count={model.Count}), the property is not in the HTML, so the component is marked defer-hydration and waits: hydrating the view takes the mark off and sets the property in the same pass.

What it refuses

Anything it cannot render the way lit would raises UnsupportedTemplateValue rather than guessing. Two of those are easy to reach for:

  • A Node in a hole. A view is the same code the browser runs, and there is no Node there. For a shadow root inside a view there is Lit.shadowRoot; anything else composes the other way round, the template's Node into the page's hole.
  • A <template> element in markup lit is going to adopt. lit never looks inside one, so every binding after it would silently never be made. Plain render and toNode write it as it stands.

There is a worked example at LitHydrationDemo.

This package has no Fable dependency: it is ordinary .NET, referenced by the server project.

Why "Unofficial"

This is a republished build of Fable.Lit by Alfonso García-Caro Núñez and its contributors, packaged from a fork so that fixes can be used before they land upstream. The toolchain is .NET 10, Fable 5, Fable.Core 4 and lit 3.

Reference this or upstream Fable.Lit, never both: they share the Lit namespace.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.21.3 97 10/6/2026
2.21.2 87 10/6/2026
2.21.1 85 10/6/2026
2.21.0 81 10/6/2026
2.20.0 85 10/6/2026
2.19.0 133 8/14/2026
2.18.0 111 8/14/2026
2.17.0 123 8/14/2026
2.16.0 118 8/14/2026
2.15.0 116 8/14/2026
2.14.0 119 8/13/2026
2.13.0 109 8/13/2026
2.12.0 108 8/13/2026
2.11.0 112 8/13/2026
2.10.0 110 8/13/2026
2.9.0 109 8/13/2026
2.8.0 114 8/13/2026
2.7.1 115 8/13/2026
2.7.0 110 8/13/2026
2.6.0 114 8/12/2026
Loading failed

Unofficial fork of Fable.Lit. Toolchain moved to .NET 10, Fable 5, Fable.Core 4 and lit 3; event handlers no longer raise FS3884. See the repository for what differs from upstream.