Guide

Everything you need to start writing reactive UI in Roblox with shARKlib. If you have used Vide or Solid, you will feel at home. If you have not, the whole library is built on three primitives and a couple of conventions.

Installation

The simplest option is the prebuilt model:

  1. Grab shARKlib.rbxm from releases.
  2. In Studio, right click ReplicatedStorage and pick Insert from File….
  3. Require it: local sh = require(ReplicatedStorage.shARKlib).

Prefer to vendor the source? Copy src/shared/shARKlib/ into your project. The folder is self-contained, no external runtime.

Building the rbxm yourself with Rojo:

rojo build shARKlib.project.json -o shARKlib.rbxm

Quick start

A counter with a button. Three concepts on display: source, create, mount.

local sh = require(ReplicatedStorage.shARKlib)

sh.SetTemplate({
  Frame     = { BackgroundColor3 = Color3.fromRGB(28, 28, 32) },
  TextLabel = { TextColor3       = Color3.fromRGB(235, 235, 240) },
})
sh.SetTemplate("primary", {
  TextButton = { BackgroundColor3 = Color3.fromRGB(34, 211, 238) },
})
sh.Init()

sh.mount(function()
  local count = sh.source(0)
  return sh.create("TextButton", "primary") {
    Size = UDim2.fromOffset(200, 40),
    Text = function() return "clicks: " .. count() end,
    Activated = function() count(count() + 1) end,
  }
end, Players.LocalPlayer.PlayerGui)

The reactive graph

There are three primitives. Everything else is built on them.

source(initial)

A mutable cell. Call with no args to read, with one arg to write. Reading inside a tracked scope creates a subscription. Writing notifies subscribers.

local n = sh.source(0)
print(n())   -- 0
n(5)
print(n())   -- 5

derive(fn)

A cached value computed from other sources. Re-runs when one of its dependencies changes. Returns a getter.

local first = sh.source("Ada")
local last  = sh.source("Lovelace")
local full  = sh.derive(function() return first() .. " " .. last() end)
print(full()) -- "Ada Lovelace"

effect(fn)

A side effect. Runs once now, and again whenever a source it reads changes. Use it for things that touch the outside world: prints, network calls, manual instance fiddling.

sh.effect(function()
  print("count is now", count())
end)

Components

A component is just a function that returns an Instance. Local state lives in a closure.

local function Toggle()
  local on = sh.source(false)
  return sh.create("TextButton") {
    Text = function() return on() and "ON" or "OFF" end,
    Activated = function() on(not on()) end,
  }
end

String props set properties. Numeric children become parented Instances. RBXScriptSignal keys connect callbacks. Function values become reactive bindings: they re-run when their dependencies change and their result is written back to the property.

Templates and Init

SetTemplate registers default properties per class. The unnamed form sets the global default. Named forms register variants you can pick later.

sh.SetTemplate({
  Frame = { BorderSizePixel = 0, BackgroundColor3 = bg },
})
sh.SetTemplate("primary", { TextButton = { BackgroundColor3 = blue } })
sh.SetTemplate("danger",  { TextButton = { BackgroundColor3 = red  } })

sh.Init()

sh.create("TextButton", "primary") { Text = "Save" }
sh.create("TextButton", "danger")  { Text = "Delete" }

Multiple SetTemplate calls merge. Later writes override earlier ones for the same key. Once you call sh.Init() the templates freeze and the rest of the API unlocks. Calling other API functions before Init is an error. Calling SetTemplate after Init is also an error.

Cleanup

Every effect, derive, and component runs inside an owner scope. When the owner is disposed (because something higher up re-evaluated, or because you called the destroy callback returned by root or mount), all of its children are disposed, all of its cleanup callbacks run, and any events it connected get disconnected. You will rarely write cleanup code yourself.

Next

Browse the full API reference for every export with signatures and examples.