JSX Authoring
Litsx treats JSX as the primary authoring format for writing Lit-based web components.
The payoff is not only syntax familiarity. It is also a more compact and more functional way to describe UI.
Flavor
The intended authored model is Lit-first:
- event listeners use
@event - property bindings use
.prop - boolean attributes use
?attr - JSX remains the authoring format even when the runtime target is Lit and web components
The TypeScript plugin and parser work together so that the IDE can reason about that syntax without forcing you to think in terms of transformed output.
What You Are Writing
When you write Litsx, you are writing:
- JSX-authored components
- backed by Lit
- executed as web components
Why This Feels Different
JSX gives Litsx two authoring advantages:
- less verbosity when expressing nested UI
- a more functional composition model for view code
In practice that means:
- small view fragments are easy to extract into functions
- conditionals and lists stay close to normal JavaScript
- component trees are usually easier to scan than heavily nested template expressions
Authoring Priorities
- clarity of authored JSX
- native Litsx primitives
- Lit-flavored bindings
- component composition in web-component land
Do not optimize for the generated code. The transform output is a runtime detail.
Lit-First Bindings
The authored syntax is easiest to understand when you can poke it directly. This example keeps the surface small on purpose:
@inputand@changeattach listeners.valuewrites to the DOM property instead of the HTML attribute?checkedtoggles a boolean attribute from state
Switch to Emitted to see how the authored JSX stays Lit-flavored while compiling down to runtime-compatible output.
Lit Directives
Litsx compiles JSX down to Lit templates, so Lit directives are part of the native authoring model.
That means two things:
- you can use built-in Lit directives such as
keyed(...),when(...),repeat(...),cache(...),guard(...), oruntil(...)directly in authored Litsx code - you should prefer those directives when the problem is already a Lit template concern, instead of trying to rebuild the same behavior with framework-specific helpers
For example:
import { keyed } from "lit/directives/keyed.js";
import { when } from "lit/directives/when.js";
function Example({ cycle, ready }) {
return (
<section>
{keyed(
cycle,
<div>{when(ready, () => <span>Ready</span>, () => <span>Loading</span>)}</div>
)}
</section>
);
}This is not a compatibility trick. It is the intended native model: Litsx owns the authored JSX surface, and Lit still owns the template runtime underneath it.
Custom directives are also supported. If you already have a directive built with Lit's directive APIs, or you want to create one for your own project, you can import and use it in Litsx the same way as any built-in directive.
In other words:
- use Lit directives when the concern belongs to templating
- use Litsx hooks and primitives when the concern belongs to component state, lifecycle, refs, async UI, or public events
This example shows that split directly:
repeat(...)owns keyed list renderingwhen(...)owns the empty-state branchkeyed(...)forces a remount when the cycle changes
Static Hoists
Litsx also supports authored static hoists with ^name(...).
This is a compile-time macro, not a runtime import:
^styles(...)is the obvious fit for component-owned CSS^properties(...)is the obvious fit for Lit property metadata^shadowRootOptions(...)or any other^name(...)can attach additional static class metadata when Lit, Litsx, or your own runtime code consumes it
Every static hoist lowers to a memoized static getter on the generated class. Static hoists also have one placement rule: they must appear as top-level statements in the component body.
The one special case is ^expose(...), which lowers to static class methods instead of to a getter. That keeps class-level imperative APIs separate from useExpose, which is still the instance-level ref primitive.