| name | solid-syntax-jsx |
| description | Use when writing JSX templates, rendering lists, conditional rendering, or using control flow components in SolidJS. Prevents ternary/map anti-patterns from React that bypass SolidJS compiled DOM optimizations. Covers Show, For, Index, Switch/Match, Dynamic, Portal, Suspense, JSX compilation model, and namespaced attributes. Keywords: Show, For, Index, Switch, Match, Dynamic, Portal, Suspense, SolidJS JSX, control flow, conditional rendering.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires SolidJS 1.x/2.x with TypeScript. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
solid-syntax-jsx
Quick Reference
JSX Compilation Model
SolidJS compiles JSX to direct DOM creation calls -- there is NO virtual DOM. Components execute once during initialization. Dynamic expressions in {} are automatically wrapped in reactive effects that perform surgical DOM updates.
React: JSX → createElement() → Virtual DOM → Diff → DOM patches
SolidJS: JSX → Compiled DOM creation → Direct DOM nodes + reactive effects
Control Flow Component Quick Reference
| Component | Import | Purpose | Key Props |
|---|
<Show> | solid-js | Conditional rendering | when, fallback, keyed |
<For> | solid-js | List rendering (objects) | each, fallback |
<Index> | solid-js | List rendering (primitives) | each, fallback |
<Switch>/<Match> | solid-js | Multi-branch conditionals | fallback / when |
<Dynamic> | solid-js/web | Dynamic component/element | component |
<Portal> | solid-js/web | Render outside DOM tree | mount, useShadow, isSVG |
<Suspense> | solid-js | Async resource loading | fallback |
Namespaced Attribute Prefixes
| Prefix | Purpose | Example |
|---|
attr: | Force HTML attribute (vs property) | <div attr:data-id={id} /> |
prop: | Set DOM property directly | <input prop:value={val} /> |
class: | Toggle individual CSS class | <div class:active={isActive()} /> |
on: | Native event listener (non-delegated) | <div on:scroll={handler} /> |
use: | Custom directive | <div use:myDirective={value} /> |
bool: | Boolean HTML attribute | <div bool:disabled={isDisabled()} /> |
@once | One-time binding (no reactivity) | <div textContent={@once val} /> |
For vs Index Decision Table
| Scenario | Use | Why |
|---|
| Array of objects, items reorder | <For> | Keyed by reference -- DOM nodes move with items |
| Array of primitives (strings, numbers) | <Index> | Keyed by index -- values update in place |
| Form inputs where position matters | <Index> | Stable index means stable DOM position |
| List where items are added/removed/moved | <For> | Reference tracking avoids recreation |
Critical Warnings
NEVER use Array.map() to render lists -- it recreates ALL DOM nodes on every array change. ALWAYS use <For> or <Index>.
NEVER use ternary operators for complex conditional rendering -- they bypass SolidJS fine-grained reactivity optimizations. ALWAYS use <Show> with fallback.
NEVER use JavaScript switch/case statements in component bodies for conditional views -- the component body runs once so the switch evaluates once and never updates. ALWAYS use <Switch>/<Match>.
NEVER pass a key prop to list items -- SolidJS ignores the key prop entirely. <For> keys by object reference automatically.
NEVER use early returns in component bodies for conditional rendering -- the component runs once so the return is permanent. ALWAYS use <Show> or <Switch>/<Match>.
JSX Compilation Details
Static vs Dynamic Optimization
The SolidJS compiler separates static and dynamic content at build time:
const Counter = () => {
const [count, setCount] = createSignal(0);
return (
<div class="counter"> {/* static -- compiled to template */}
<span>Count: {count()}</span> {/* "Count: " static, {count()} dynamic */}
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
};
Attribute Declaration Order
SolidJS evaluates attributes in declaration order -- later definitions override earlier ones:
<input type="text" {...defaultProps} value={currentValue} />
Style Object Syntax
ALWAYS use double curly braces and kebab-case property names:
<button style={{ color: "red", "font-size": "2rem" }}>Click</button>
Show -- Conditional Rendering
Renders children when when is truthy, otherwise renders fallback:
import { Show } from "solid-js";
<Show when={isLoggedIn()} fallback={<LoginForm />}>
<Dashboard />
</Show>
<Show when={user()}>
{(u) => <p>Welcome, {u().name}</p>}
</Show>
<Show when={user()} keyed>
<UserProfile user={user()} />
</Show>
Behavior: Without keyed, only truthiness changes trigger updates. With keyed, reference changes also trigger re-render.
For -- List Rendering (Objects)
Renders a list keyed by object reference. ALWAYS use for arrays of objects:
import { For } from "solid-js";
<For each={todos()} fallback={<p>No todos yet</p>}>
{(todo, index) => (
<div>
<span>#{index() + 1}</span> {/* index is a SIGNAL -- call it */}
<span>{todo.text}</span> {/* item is a direct VALUE */}
</div>
)}
</For>
Callback signature: (item: T, index: () => number) => JSX.Element
item -- direct value (T), NOT a signal
index -- signal, MUST call as index() to read
Index -- List Rendering (Primitives)
Renders a list keyed by array index. ALWAYS use for arrays of primitives:
import { Index } from "solid-js";
const [names, setNames] = createSignal(["Alice", "Bob", "Charlie"]);
<Index each={names()}>
{(name, i) => (
<input
value={name()} {/* item is a SIGNAL -- call it */}
onInput={(e) => {
setNames(prev => {
const next = [...prev];
next[i] = e.target.value; {/* index is a plain NUMBER */}
return next;
});
}}
/>
)}
</Index>
Callback signature: (item: () => T, index: number) => JSX.Element
item -- signal, MUST call as item() to read
index -- direct number, NOT a signal
This is the OPPOSITE of <For>.
Switch/Match -- Multi-Branch Conditionals
Evaluates <Match> children sequentially -- first truthy when wins:
import { Switch, Match } from "solid-js";
<Switch fallback={<NotFound />}>
<Match when={route() === "home"}><HomePage /></Match>
<Match when={route() === "about"}><AboutPage /></Match>
<Match when={route() === "settings"}><SettingsPage /></Match>
</Switch>
<Switch>
<Match when={user()}>
{(u) => <UserProfile user={u()} />}
</Match>
<Match when={error()}>
{(e) => <ErrorDisplay error={e()} />}
</Match>
</Switch>
Behavior: Only ONE <Match> renders at a time (mutual exclusivity).
Dynamic -- Dynamic Component/Element Selection
import { Dynamic } from "solid-js/web";
const [currentView, setCurrentView] = createSignal<Component>(HomeView);
<Dynamic component={currentView()} user={currentUser()} />
const [tag, setTag] = createSignal("h1");
<Dynamic component={tag()}>Hello World</Dynamic>
function Box(props: { as?: string | Component; children: JSX.Element }) {
const merged = mergeProps({ as: "div" }, props);
return <Dynamic component={merged.as}>{props.children}</Dynamic>;
}
The component prop accepts SolidJS components, HTML tag strings, or JSX intrinsic element keys. All additional props are forwarded.
Portal -- Render Outside DOM Tree
import { Portal } from "solid-js/web";
<Portal mount={document.getElementById("modal-root")!}>
<div class="modal-overlay">
<div class="modal-content">Modal content here</div>
</div>
</Portal>
Key behavior: Events propagate through the component tree, not the DOM tree. Client-side only -- hydration is disabled for portals.
Suspense -- Async Resource Loading
Tracks all createResource calls within its boundary:
import { Suspense } from "solid-js";
<Suspense fallback={<LoadingSpinner />}>
<DataView data={dataResource()} />
</Suspense>
Key advantage over Show: <Show when={resource()}> destroys and recreates DOM. <Suspense> creates DOM nodes immediately but delays attachment -- preserving state and avoiding flicker.
Nested Suspense boundaries resolve independently:
<Suspense fallback={<HeaderSkeleton />}>
<Header />
<Suspense fallback={<ContentSkeleton />}>
<Content />
</Suspense>
</Suspense>
Common Anti-Patterns (Summary)
| React Pattern (WRONG) | SolidJS Pattern (CORRECT) | Why |
|---|
{items.map(i => ...)} | <For each={items()}> | map recreates all DOM nodes |
{cond ? <A/> : <B/>} | <Show when={cond} fallback={<B/>}> | Show optimizes DOM mutations |
switch(x) { case: ... } | <Switch><Match when={...}> | Component body runs once |
<div key={id}> | No key needed | For keys by reference |
if (!data) return <Loading/> | <Show when={data} fallback={...}> | Early return is permanent |
Reference Links
- references/methods.md -- API signatures for Show, For, Index, Switch/Match, Dynamic, Portal, Suspense
- references/examples.md -- Control flow patterns, conditional rendering, list rendering, portals, suspense
- references/anti-patterns.md -- Array.map, ternary, switch/case, key prop -- all JSX anti-patterns
Official Sources