Fable.Three 0.186.1

dotnet add package Fable.Three --version 0.186.1
                    
NuGet\Install-Package Fable.Three -Version 0.186.1
                    
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="Fable.Three" Version="0.186.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fable.Three" Version="0.186.1" />
                    
Directory.Packages.props
<PackageReference Include="Fable.Three" />
                    
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 Fable.Three --version 0.186.1
                    
#r "nuget: Fable.Three, 0.186.1"
                    
#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 Fable.Three@0.186.1
                    
#: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=Fable.Three&version=0.186.1
                    
Install as a Cake Addin
#tool nuget:?package=Fable.Three&version=0.186.1
                    
Install as a Cake Tool

Fable.Three

CI NuGet

Write three.js 3D scenes in F#.

Fable.Three lets you use three.js from Fable, the F#-to-JavaScript compiler. Every three.js class, method and option has an F# type. Your editor autocompletes them and shows three.js's own documentation as you type, and the compiler catches mistakes before you open a browser. The JavaScript that comes out is what you would have written by hand.

The Fable.Three demo: a stack of cartons

Try the demo. It is one F# file.

Getting started

You need the .NET SDK (8 or later) and Node.js (18 or later). These steps take you from an empty folder to a turning cube.

1. Create a folder and install the tools.

mkdir hello-three && cd hello-three
dotnet new tool-manifest
dotnet tool install fable
npm init -y
npm install three@0.186 vite

Fable turns your F# into JavaScript. Vite serves the page and reloads it when the code changes.

2. Add three files.

App.fsproj, the F# project:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="App.fs" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Fable.Three" Version="0.186.*" />
  </ItemGroup>
</Project>

App.fs, the scene:

module App

open Browser
open Three

// A scene, a camera to look at it, and a renderer that draws into the page.
let scene = Scene()
scene.background <- Color(0xf2eee6)

let camera = PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.z <- 3.

let renderer = WebGLRenderer(antialias = true)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement) |> ignore

// A cube, and light to see it by.
let cube = Mesh(BoxGeometry(1, 1, 1), MeshStandardMaterial(color = 0xd4532e))
let sun = DirectionalLight(0xffffff, 3)
sun.position.set(1, 2, 3) |> ignore
scene.add(cube, sun, AmbientLight(0xffffff, 0.5)) |> ignore

// Turn the cube a little on every frame.
renderer.setAnimationLoop(fun time ->
    cube.rotation.x <- time / 2000.
    cube.rotation.y <- time / 1000.
    renderer.render(scene, camera))

index.html, the page that loads it:

<!doctype html>
<html>
  <body style="margin: 0">
    <script type="module" src="./build/App.js"></script>
  </body>
</html>

3. Run it.

dotnet fable watch -o build --run npx vite

Open http://localhost:5173 and you should see an orange cube turning. Change something in App.fs, save, and the page reloads with your change.

Adding it to an existing Fable project

dotnet add package Fable.Three
npm install three@0.186

With Paket, it's paket add Fable.Three instead. With Femto, running dotnet femto installs the matching three.js for you.

From JavaScript to F#

If you know three.js, you already know Fable.Three: the classes, methods and properties have the same names. What changes is the F# syntax.

JavaScript F#
new THREE.Mesh(geometry, material) Mesh(geometry, material)
new THREE.MeshStandardMaterial({ color: 0xff0000 }) MeshStandardMaterial(color = 0xff0000)
mesh.position.x = 2 mesh.position.x <- 2.
material.side = THREE.DoubleSide material.side <- Side.DoubleSide
import { OrbitControls } from "three/addons/controls/OrbitControls.js" open Three.Addons
if (object instanceof THREE.Mesh) match object with :? Mesh as mesh -> ...

The rest of this section goes through each of these.

Creating objects

Leave out new and THREE.:

let geometry = BoxGeometry(1, 1, 1)
let mesh = Mesh(geometry, MeshNormalMaterial())

Options become named arguments

Where three.js takes a settings object, such as { color: 0xff0000, roughness: 0.5 }, you pass the settings as named arguments. Leave out any you don't need.

let material = MeshStandardMaterial(color = 0xff0000, roughness = 0.5, metalness = 0.1)
let renderer = WebGLRenderer(antialias = true, alpha = true)

A colour can be a hex number (0xff0000), a CSS colour ("tomato", "#ff6347") or a Color.

Setting properties and calling methods

Properties are set with <-:

mesh.position.x <- 2.
material.transparent <- true

Many three.js methods return the object they were called on so JavaScript can chain them, for example position.set(1, 2, 3) or scene.add(mesh). F# asks you to say when you're not using a result, so put |> ignore after them:

mesh.position.set(1, 2, 3) |> ignore
scene.add(mesh) |> ignore

Constants

three.js constants are grouped by what they're for. Type the group name and a dot, and your editor lists the choices:

material.side <- Side.DoubleSide
texture.wrapS <- Wrapping.RepeatWrapping
renderer.toneMapping <- ToneMapping.ACESFilmicToneMapping

A few three.js properties are declared as plain strings or numbers. For those, use the constant of the same name from the Constants module:

renderer.outputColorSpace <- Constants.SRGBColorSpace

Addons

Everything three.js ships under three/addons is in Three.Addons: camera controls, model loaders, post-processing, 2D and 3D labels and more. Each one is imported from its own file, so your bundle only contains the ones you use.

open Three.Addons

let controls = OrbitControls(camera, renderer.domElement)
controls.enableDamping <- true

Loading textures and models

Callbacks are ordinary F# functions, and their argument has the right type:

let crate = TextureLoader().load("crate.png")

GLTFLoader().load("robot.glb", fun gltf -> scene.add(gltf.scene) |> ignore)

With the Fable.Promise package you can use loadAsync instead:

promise {
    let! gltf = GLTFLoader().loadAsync("robot.glb")
    scene.add(gltf.scene) |> ignore
}

Checking what kind of object you have

:? checks an object's type. For example, when the mouse is over a mesh:

let raycaster = Raycaster()

let highlight (pointer: Vector2) =
    raycaster.setFromCamera(pointer, camera)

    for hit in raycaster.intersectObjects(scene.children, true) do
        match hit.``object`` with
        | :? Mesh as mesh -> mesh.scale.setScalar(1.2) |> ignore
        | _ -> ()

object is a reserved word in F#, which is why the property is written hit.``object``. The same goes for any three.js name that is also an F# keyword, such as renderer.shadowMap.``type``.

Things that work a little differently

Numbers

Every three.js number is an F# float. You can pass whole numbers where three.js expects one, as in BoxGeometry(1, 1, 1), but when you calculate with numbers use floats: time / 1000., not time / 1000.

A mesh's material

A Mesh can hold any kind of material, so mesh.material has the general type Material. To change something only a particular material has, keep your own reference to it:

let paint = MeshStandardMaterial(color = 0x3080ff)
let ball = Mesh(SphereGeometry(1), paint)
paint.roughness <- 0.2

Or tell F# which material it is:

(ball.material :?> MeshStandardMaterial).roughness <- 0.2

Properties that take different kinds of value

Some properties accept more than one type. scene.background, for example, can be a Color or a Texture, and you can assign either:

scene.background <- Color(0xf2eee6)
scene.background <- crate

When the value is a more specific kind than the property names, such as a CubeTexture (which is a kind of Texture), F# needs a nudge. Put !^ in front of it, which comes from open Fable.Core.JsInterop:

scene.background <- !^ CubeTextureLoader().load(sky)

Values that may be missing

An object that might be missing is null, as in JavaScript, so check with isNull. A number or function that three.js may leave out comes back as an F# option.

if not (isNull mesh.parent) then mesh.removeFromParent() |> ignore

Your own data, and shader uniforms

userData and a shader's uniforms are keyed by name, like a dictionary:

mesh.userData["id"] <- box 42

let shader =
    ShaderMaterial(
        uniforms = Record.ofSeq [ "time", IUniform(value = 0.) ],
        vertexShader = vertexSource,
        fragmentShader = fragmentSource
    )

shader.uniforms["time"].value <- time

What's included

  • All of three for the WebGL renderer: every class, material, geometry, light, loader, helper, curve and math type, including animation and WebXR.
  • All of three/addons: controls, loaders for glTF, DRACO, KTX2, HDR, FBX, OBJ, STL and more, post-processing effects, 2D/3D HTML labels, thick lines, exporters and extra geometries.
  • three.js's documentation in your editor's tooltips. Anything three.js has deprecated is marked, so the compiler warns you when you use it.

Not included yet: the WebGPU renderer and TSL node materials (three/webgpu, three/tsl).

Fable.Three 0.186.x is for three.js 0.186 (r186). It works with Fable 4 and 5.

Contributing

The F# bindings are generated from three.js's TypeScript type definitions, so don't edit Three.fs or Three.Addons.fs by hand. Change the generator in tools/generate instead and regenerate:

npm ci && dotnet tool restore
npm run generate   # rewrites src/Fable.Three/Three.fs and Three.Addons.fs
npm test           # compiles the tests with Fable and runs them against three.js
npm run demo       # the demo, with live reload

docs/DESIGN.md explains how TypeScript's types become F# ones, and why.

License

MIT. three.js is © its authors and MIT licensed. The documentation comments come from @types/three, also MIT.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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
0.186.1 56 9/26/2026
0.186.0 56 9/26/2026