Values
Refs are PeanutUI's reactive primitive. A ref holds a value, and anything reading it updates when it changes.
PeanutUI.Ref
PeanutUI.Computed
PeanutUI.Reactive
PeanutUI.getRef
PeanutUI.toRef
PeanutUI.isRef
PeanutUI.isEqual
PeanutUI.unwrapCreating a ref
const count = PeanutUI.Ref(0)PeanutUI.Ref is the constructor itself, so you call it directly.
Reading and writing
Use .value:
print(count.value) -- 0
count.value = 1
print(count.value) -- 1Numbers support +=:
count.value += 1Watching
watch runs a callback when the value changes:
const unwatch = count.watch(function(value)
print("Value changed:", value)
end)
count.value = 2
-- Next scheduler tick...
-- watcher prints "Value changed: 2"\
-- And then you can unwatch
-- if you unwatch in the same tick as watch, callback won't run
unwatch()Returning true from the callback unwatches automatically:
count.watch(function(value)
print("Once:", value)
return true
end)PeanutUI.watch is the standalone form and also accepts a reactive table. See Watch & WatchEffect for the full API.
WatchEffect
watchEffect runs a callback immediately and re-runs it whenever any ref read inside it changes. Dependencies are tracked automatically, so there is no explicit dependency list:
const stop = PeanutUI.watchEffect(function()
print("Count is " .. count.value)
end)
count.value = 1 -- prints "Count is 1"
stop()Use it for side effects — writing to instances, logging, imperative calls. See Watch & WatchEffect for the full API.
Reactive tables
PeanutUI.Reactive wraps a table in a deep reactive proxy:
const state = PeanutUI.Reactive({ health = 100 })
state.health -= 10 -- triggers updateRead a field as a ref with PeanutUI.toRef:
const health = PeanutUI.toRef(state, "health")
print(health.value)Use a shallow proxy when you don't want nested tables to trigger recalculations:
const state = PeanutUI.Reactive({ items = {} }, true)Computed
PeanutUI.Computed derives a value from other refs:
const a = PeanutUI.Ref(2)
const b = PeanutUI.Ref(3)
const sum = PeanutUI.Computed(function()
return a.value + b.value
end)
print(sum.value) -- 5
a.value = 5
print(sum.value) -- 8Computed values are lazy — they only recompute when something reads them and a dependency changed. See Computed for details.
Equality shortcut
PeanutUI.isEqual creates a computed boolean comparing a ref to a value or another ref:
const isTouch = PeanutUI.isEqual(PeanutUI.preferredInput, "touch")
print(isTouch.value)Unwrapping values
PeanutUI.unwrap accepts either a ref or a plain value and returns the plain value. If it's given a ref, it reads .value (tracking it reactively); otherwise it returns the value unchanged:
const a = PeanutUI.Ref(2)
print(PeanutUI.unwrap(a)) -- 2
print(PeanutUI.unwrap(5)) -- 5This is useful when a value may or may not be a ref — for example a prop typed as ValueOrRef<T>. Reading it inside a computed tracks the ref only when one was passed:
const someValue: ValueOrRef<number> = a
const doubled = PeanutUI.Computed(function()
return PeanutUI.unwrap(someValue) * 2
end)Global state
PeanutUI.preferredInput is a reactive ref holding the player's preferred input:
print(PeanutUI.preferredInput.value) -- "gamepad" | "desktop" | "touch"PeanutUI.globalSignals exposes the framework's global signals.
Deferred updates
Ref changes are deferred — they don't hit Roblox instances immediately. Don't assume a property is updated the instant you set a ref.