Runtime API - Krate Docs
You're viewing an older version ().
Krate Docs

Every export from @krate/runtime.

#Runtime API

Everything the client runtime exports from @krate/runtime.

#Reactive primitives

#createSignal

const [getValue, setValue] = createSignal(initialValue)
getValue()           // read current value
setValue(next)        // set value (triggers subscribers)
setValue(prev => ...) // functional update

#Persistent state (persist)

Pass a persist option to survive reloads. The value is read from storage when the signal is created and written on every change. This is SSR-safe (storage is only touched in the browser) and syncs across tabs by default.

// localStorage under the key "count"
const [count, setCount] = createSignal(0, { persist: 'count' })

// sessionStorage, or full control
const [theme, setTheme] = createSignal('light', {
  persist: {
    key: 'theme',
    store: 'session',            // 'local' (default) | 'session' | 'memory' | Storage
    serialize: JSON.stringify,   // optional
    deserialize: JSON.parse,     // optional
    sync: false,                 // cross-tab sync (default true for 'local')
  },
})

If storage is unavailable (private mode, quota exceeded) the signal falls back to an in-memory store instead of throwing; clear a stored value with clearPersisted.

#Durable SSR state

When a page embeds window.__KRATE_STATE__ (a JSON object keyed by persistence key), that server-chosen value takes precedence over browser storage on the first hydration, so the hydrated value matches what the server rendered. A project can provide it with a krate.state.json file at the root, which the build injects into every page.

#createCSSChoice / createCSSToggle / createCSSFlags

Zero-JS state compiled to hidden inputs + :has() CSS. createCSSChoice is a radio group, createCSSToggle a single checkbox, and createCSSFlags independent checkboxes. None of them ship client JavaScript; a declaration that cannot be compiled is a build error (use createSignal instead).

const [tab, setTab]   = createCSSChoice('overview')            // options inferred
const [tab, setTab]   = createCSSChoice('a', ['a', 'b', 'c'])  // explicit options
const [dark, setDark] = createCSSToggle(false)
const [flags, setFlag] = createCSSFlags(['bold', 'italic'])     // flags.bold()

#createCSSGroup

An optional radio group with an explicit closed (null) state — accordions, disclosures, dialogs, popovers, dropdown menus.

const [open, setOpen] = createCSSGroup(null, { as: 'accordion' })
setOpen('faq'); setOpen(null)

#createCSSRange

A discrete integer range — steppers, stepped sliders, progress indicators. set(n() +/- 1) compiles to bounded stepper labels.

const [step, setStep] = createCSSRange(0, { min: 0, max: 4, step: 1 })

#createCSSStack

A drill-down navigation stack over a declared tree; push targets are literals.

const [stack, { push, pop, clear }] = createCSSStack(['root'])
stack.top(); push('settings'); pop(); clear()

#Live text & vars

Reading a getter as text ({tab()}) compiles to a zero-JS live value backed by the inherited --krate-current property. vars publishes more custom properties per option (values verbatim; quote strings for content:).

const [plan, setPlan] = createCSSChoice('solo', ['solo', 'pro'])
<p>Plan: {plan()}</p>

const [tier, setTier] = createCSSChoice('solo', {
  options: ['solo', 'pro'],
  vars: { '--price': { solo: '"$9"', pro: '"$29"' } },
})

#ARIA options (as)

Every CSS primitive accepts an options object with an as role preset (radio/tabs/listbox/menu/accordion/disclosure/dialog/popover/ switch), plus label and raw aria overrides. Native presets stay zero-JS; tabs/listbox/disclosure presets inject a tiny ARIA synchroniser (no page hydration bundle).

const [tab, setTab] = createCSSChoice('a', { as: 'tabs' })
const [q, setQ]     = createCSSGroup(null, { as: 'dialog', aria: { label: 'Query' } })
const [t, setT]     = createCSSChoice('a', { as: 'tabs', aria: false }) // opt out

See CSS Signals.

#createReducer

A createSignal whose setter applies a reducer. Returns [getter, dispatch], so the getter reads like any other signal (and hydrates identically). This is also the target of React's useReducer.

const [count, dispatch] = createReducer((state, action) => state + action, 0)
count()          // read current state
dispatch(1)      // reduces: state = reducer(state, action)

#createEffect

const dispose = createEffect(() => { /* re-runs when deps change */ })
dispose()            // remove the effect entirely

#createMemo

const value = createMemo(() => expensiveCalc(input()))
value()              // read memoized value

#onCleanup

onCleanup(() => { /* runs before effect re-execution and on dispose */ })

#onMount

onMount(() => { /* runs once after the initial render */ })

#forwardRef

const FancyInput = forwardRef((props, ref) => {
  ref((el: Element) => el.focus());
  return h('input', props);
});

Forwards a ref callback through to the underlying DOM element.

#disposeAll

disposeAll()

Disposes every live effect (used internally on SPA navigation and in test harnesses).

#DOM helpers

#h — hyperscript

const el = h('div', { class: 'foo', onClick: () => {} }, child1, child2)
// Functions as children become effects:
//   h('span', null, () => signal())

#mount

mount(() => h(App, null), '#root')

#hydrate

hydrate(() => h(App, null), '#root')

Hydrates SSR content, binding effects to existing DOM via data-k/data-kh attributes.

#insert / clearNodes

insert(parent, value, startMarker)   // replace content between two <!--k--> markers
clearNodes(start, end)               // clear nodes between two markers

Low-level helpers used by the compiled hydration code to manage dynamic regions delimited by <!--k--> comment markers.

#JSX runtime

import { jsx, jsxs, Fragment } from '@krate/runtime/jsx-runtime'

Automatic JSX transform — use <div> syntax in TSX files.

Every JSX element accepts a showIf prop (alias visibleIf) for conditional rendering — sugar for {expr && <el/>}, compiled away at build time:

<div showIf={count() > 0}>Shown when count is positive</div>

With the zero-JS CSS primitives, showIf also accepts compound boolean logic over state — &&, ||, !, === / ==, !== / != — across one or more scopes, compiled to :has() selector chains:

<div showIf={plat() === 'mac' && licensed()}>Approve</div>
<div showIf={plat() === 'win' || !licensed()}>Upgrade</div>

Equivalent expressions (commuted operands, De Morgan) dedupe to one wrapper class; unclassifiable expressions remain a build error.

#SPA router

import { initRouter } from '@krate/runtime'
initRouter()

Call once to enable client-side navigation via data-krate-link anchors and <Link>. Handles pushState, popstate, stylesheet diffing, and tree reconciliation. On navigation the router diffs the live content root against the parsed new page via reconcileTrees — unchanged nodes (keyed by <!--k:--> comment markers, data-k attributes, and id anchors) are kept in place so their state (focus, scroll, media, CSS animations) survives. Effects are disposed before the diff and stale __krate_* handler props are stripped; the new page's hydration JS then rebinds the kept nodes. Emits a krate:navigate CustomEvent after each navigation.

reinitRouter() tears down and re-registers the router listeners — useful after a full page replacement or for HMR in dev.

Programmatic navigation:

import { navigate } from '@krate/runtime'
navigate('/about', { replace: false, scroll: true })

navigate returns false when handled client-side and true when it fell back to a full page load (router not initialized, or a cross-origin URL). The router also emits krate:navigate-start before the DOM mutation and krate:navigate-end after it — a pairing that powers View Transitions and progress UI — plus krate:navigate after history/scroll updates. Elements with data-view-transition="name" are given a matching view-transition-name, and the router wraps the mutation in document.startViewTransition when supported (honoring prefers-reduced-motion).

#Context

import { createContext } from '@krate/runtime'

const ThemeCtx = createContext('light')
// ThemeCtx.Provider — wraps children with a context value
// ThemeCtx.useContext() — reads nearest Provider value
// ThemeCtx.defaultValue — fallback when no Provider

#Resources

import { createResource } from '@krate/runtime'

const [user, { mutate, refetch }] = createResource(
  () => userId(),                          // reactive source
  async (id) => fetch(`/api/user/${id}`).then(r => r.json())  // fetcher
)

user()          // current data (or undefined)
user.loading    // boolean
user.error      // error or undefined
user.state      // 'unresolved' | 'loading' | 'ready' | 'error' | 'refreshing'
mutate(prev => ({ ...prev, name: 'new' }))  // optimistic update
refetch()       // trigger re-fetch

#Class helpers & Slot

Dependency-free implementations of the helpers shadcn/ui-style components use. Literal cn/clsx/cva calls are folded by the compiler; these runtime versions cover dynamic cases.

import { cn, clsx, cva, twMerge, Slot, cloneElement } from '@krate/runtime'

cn('p-4', 'px-2')                       // 'px-2 p-4' (later utilities win)
clsx('a', cond && 'b', { c: true })     // 'a c'
const b = cva('base', { variants: { size: { sm: 'h-8', lg: 'h-10' } } })
b({ size: 'lg' })                       // 'base h-10'
Slot({ children })                      // merges props onto a single child
cloneElement(node, { class: 'x' })      // apply props to an existing node

Slot implements the asChild mechanic: it renders its single child element instead of wrapping it, merging the Slot's props (including class) onto it.

#React compatibility

The compiler rewrites React hooks and React.* calls to the primitives above at build time, so React source runs unchanged (no React runtime is shipped). In particular:

  • useState → createSignal, useEffect → createEffect, useMemo → createMemo, useReducer → createReducer.
  • useRef → { current: initial }, useCallback → the function, useContext → Context.useContext().
  • memo/lazy → identity, createElement → h, <Fragment> → a JSX fragment.
  • useId → a stable per-instance build-time literal.
  • Bare reads ({count}) are auto-called (count()).

See the React Compatibility guide for the full list and the intentionally unsupported cases.

#Type exports

import type {
  Component, PropsWithChildren, ComponentProps,
  RefCallback, Context, ResourceReturn, ResourceActions,
} from '@krate/runtime'

Last updated: 2026-10-16

Contributors: Noinkin