Utils
Kiru ships a small set of runtime utilities that make it easier to work with signals, DOM updates, and development tooling.
className is a tiny helper for building class strings from optional fragments.
It accepts a variadic list of string | false | null | undefined and joins the
truthy ones with spaces.
import { className as cls } from "kiru/utils"
const MyButton = () => {
const isActive = signal(false)
return () => (
<button
className={cls("btn", "btn-primary", isActive.value && "btn-active")}
onclick={() => isActive.value = !isActive.value}
>
Click me
</button>
)
}flushSync forces Kiru to synchronously flush any pending work. If there is no
work queued, it does nothing.
import { flushSync } from "kiru"
function startViewTransition() {
document.startViewTransition(() => {
// enqueue some state updates...
// ensure all DOM changes are applied during the transition:
flushSync()
})
}
Use this sparingly, as it can bypass Kiru's normal scheduling optimisations. It is primarily intended for integrations with things like View Transitions or measurements that must see the latest DOM.
nextIdle lets you schedule work to run after any currently queued Kiru updates
have finished, or immediately if the scheduler is already idle.
import { nextIdle } from "kiru"
nextIdle(() => {
// Runs after the current render pass has completed
console.log("DOM is up to date")
})This is useful for coordinating with APIs that should see a fully-flushed DOM, without forcing an immediate synchronous flush.
onHmr registers a callback to run when Hot Module Reload (HMR) happens in
development. In production or non-browser environments it is a no-op.
import { onHmr } from "kiru"
// Start an interval in the module scope
const interval = setInterval(() => {
console.log("tick")
}, 1000)
// Ensure the interval is cleaned up whenever this file is reloaded
onHmr(() => clearInterval(interval))- When called during module evaluation (top-level), the callback runs the next time that module is replaced.
- When called later (e.g. inside a handler), the callback runs on the next HMR update, regardless of which module changed.
- Each registered callback is only fired once.
onHmr is most useful for cleaning up resources created at module scope, such
as intervals, subscriptions, or singletons that should not leak across hot
reloads.
unwrap takes either a plain value or a Signal<T> and returns the underlying value.
It is particularly useful when consuming "signalable" values - props or options
typed as Signal<T> | T - so your code can treat both forms uniformly.
import { signal, unwrap } from "kiru"
const count = signal(0)
unwrap(count) // 0 (non‑reactive, does not track)
unwrap(42) // 42Pass true as the second argument to read reactively when the input is a signal:
const doubled = unwrap(count, true) * 2unwrap(value)- ifvalueis a signal, returnsvalue.peek()(no tracking).unwrap(value, true)- ifvalueis a signal, returnsvalue.value(tracks).
For example, a component or helper that accepts a "signalable" prop can normalize it:
interface CounterProps {
initialCount?: number | Signal<number>
}
const Counter: Kiru.FC<CounterProps> = () => {
const $ = setup<typeof Counter>()
const count = $.derive((props) => unwrap(props.initialCount, true) ?? 0)
return () => (
<button onclick={() => count.value++}>Count: {count}</button>
)
}