Primitives
Litsx has its own public surface. The important thing is the authored API of the framework, not the code shape produced by transforms.
Core Runtime Surface
These are the primitives that matter most when you write Litsx directly:
ErrorBoundarySuspenseBoundarySuspenseList
The runtime also exposes helpers such as ensureLazyElement(...), but those are supporting pieces. They are relevant for transforms and advanced integration, not for day-one authoring.
litsx/jsx-runtime and litsx/jsx-dev-runtime are part of the tooling story, not the authored runtime surface. They matter for jsxImportSource: "litsx" and editor integration; see Tooling.
For everyday component work, the runtime surface also includes the hooks that model state, DOM access, and effect timing:
useState(...)useMemoValue(...)useRef(...)useAsyncState(...)useOptimistic(...)useOnConnect(...)useEvent(...)useEmit(...)useOnCommit(...)useAfterUpdate(...)
This example shows that split directly in one component:
useState(...)keeps the counter alive across rendersuseRef(...)captures the rendered buttonuseOnCommit(...)does immediate DOM work on the commit pathuseAfterUpdate(...)runs a little later as passive post-update work
useMemoValue(...), usePrevious(...), and useControlledState(...) extend that authored surface without changing the same core model:
useMemoValue(...)keeps expensive derived values render-pureusePrevious(...)lets render logic compare against the previous frameuseControlledState(...)supports controlled/uncontrolled library components
useAsyncState(...) and useOptimistic(...) cover two related but separate async jobs:
useAsyncState(...)owns authoritative async mutations, pending, latest error, and resetuseOptimistic(...)layers a temporary optimistic overlay on top of authoritative state
Use useAsyncState(...) when the component needs to drive a real async state transition such as save, refresh, or confirm. Use useOptimistic(...) when the UI should show a temporary expected outcome before authoritative state catches up.
That split matters:
useAsyncState(...)owns authoritative async mutation stateuseOptimistic(...)owns a disposable optimistic overlaySuspenseBoundaryowns async reads that pause render
If the question is "what should the UI show while we wait for a mutation?", reach for useAsyncState(...) and optionally useOptimistic(...).
If the question is "what should this subtree show while render-time data is still unresolved?", reach for SuspenseBoundary.
Authoritative Async State
This example focuses on the authoritative side of an async mutation:
- the saved value is the source of truth
pendingreflects in-flight workerroris part of the same state surfacereset()restores the authoritative state
Use this pattern when the component is coordinating a real async transition such as save, retry, or confirm.
Optimistic Overlay
This example focuses on the temporary overlay:
- the left column shows the authoritative state
- the right column shows what the UI renders with optimistic values applied
- the optimistic layer can be discarded explicitly
- when authoritative state changes, the overlay re-anchors to it
Use this pattern when the UI should briefly show an expected outcome before the real state catches up.
Authoring Model
Litsx is Lit-flavored at the authored level:
- event listeners use
@event - property bindings use
.prop - boolean attributes use
?attr - Lit directives remain first-class in template expressions
- components are authored in JSX and compiled down to Lit-compatible output
That last point matters more than it may seem: when a problem is already a Lit template problem, the right answer is usually still a Lit directive.
So in native Litsx code:
- use built-in directives such as
keyed(...),when(...),repeat(...),cache(...),guard(...), anduntil(...)when they fit - use custom directives too, when your project already has them or needs them
- do not treat directives as something "outside" Litsx; they are part of the intended authoring stack
Connection-Scoped Work
useOnConnect(...), useEvent(...), and useEmit(...) cover three different event jobs:
useOnConnect(...)registers and cleans up resources tied to the host being connecteduseEvent(...)gives those resources a stable callback that still sees fresh state and propsuseEmit(...)publishes publicCustomEvents from the current host without reaching forthis.dispatchEvent(...)
Use useOnConnect(...) by itself when re-arming the resource on dependency changes is acceptable.
Pair it with useEvent(...) when you want to register once and avoid stale closures in listeners owned by window, document, observers, or other imperative APIs.
Reach for useEmit(...) when the component needs to publish a DOM event as part of its public API, such as change, select, or open.
The playground above focuses on render-time and post-render hooks. useEvent(...) fits the same runtime surface, but it matters most when the component also owns connection-scoped listeners or subscriptions.
Controlled State
useControlledState(...) is mainly for reusable components and design-system APIs. It lets the same component work with:
valuecontrolled from outsidedefaultValueowned locallyonChangenotifications in both modes
Suspense Primitives
SuspenseBoundary and SuspenseList are native Litsx primitives for asynchronous UI.
SuspenseBoundaryowns fallback/content rendering and reveal phasesSuspenseListcoordinates reveal order across sibling boundaries- both are designed around light DOM so styles can flow through from the containing component
Failure Boundaries
ErrorBoundary is the native Litsx primitive for recoverable synchronous render failures.
- it catches one subtree failing without taking down the whole component
- it keeps fallback UI latched once the error has been captured
- to retry, give the boundary a new identity with Lit's
keyed(...)