Async UI
Litsx models asynchronous UI with native primitives:
SuspenseBoundarySuspenseListuseAsyncState(...)useOptimistic(...)
Lazy loading fits into the same model: an async region resolves behind a boundary, and the boundary decides when fallback or content should be shown.
Design Direction
- suspense is expressed as components, not opaque helper calls
- async component loading resolves through boundaries instead of through a separate authored API surface
SuspenseListcoordinates reveal order in light DOM- async reads and async mutations are treated as different jobs
What These Primitives Do
SuspenseBoundaryowns fallback rendering for one async regionSuspenseListcoordinates how several sibling boundaries revealuseAsyncState(...)owns authoritative async mutations started by the useruseOptimistic(...)layers temporary optimistic UI over authoritative state- both primitives live in the authored component tree, so their layout and styling remain part of normal Litsx composition
Reads vs Mutations
Litsx keeps async reads and async mutations separate on purpose.
- Use
SuspenseBoundarywhen rendering may pause because data or a lazy dependency is not ready yet. - Use
useAsyncState(...)when an event starts asynchronous work such as save, refresh, confirm, or retry. - Use
useOptimistic(...)when the UI should temporarily show the expected result of that mutation before the authoritative state catches up.
That means:
SuspenseBoundaryis for "this render cannot finish yet"useAsyncState(...)is for "this action is in flight"useOptimistic(...)is for "show the expected result while that action is in flight"
useTransition(...) still fits underneath that model as a pending/priority primitive. It does not replace useAsyncState(...), and it does not solve optimistic UI by itself.
Example
This playground keeps the async model deliberately small:
- each profile panel resolves on its own delay
- each one sits behind its own
SuspenseBoundary <SuspenseList reveal-order="forwards">prevents the later panel from revealing ahead of the earlier one
Use the replay button to watch the list coordinate the reveal sequence again.
When To Use Them
Use SuspenseBoundary when one part of a component may pause independently of the rest of the view.
Use SuspenseList when several async sections belong to the same reading flow and should reveal in a predictable order.
Use useAsyncState(...) when the user starts async work and the component must track pending, error, reset, and the latest committed result.
Use useOptimistic(...) when that same interaction should also render a temporary optimistic overlay before authoritative state catches up.