| 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, render list, if else in JSX, loop over array, show hide element. | ||||
| license | MIT | ||||
| compatibility | Designed for Claude Code. Requires SolidJS 1.x/2.x with TypeScript. | ||||
| metadata |
|
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
| 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 |
| 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} /> |
| 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 |
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>.
The SolidJS compiler separates static and dynamic content at build time:
// Static parts: template created once, cloned for each instance
// Dynamic parts: reactive effects update only the specific text node or attribute
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>
);
};SolidJS evaluates attributes in declaration order -- later definitions override earlier ones:
// value={currentValue} ALWAYS overrides any value in defaultProps
<input type="text" {...defaultProps} value={currentValue} />ALWAYS use double curly braces and kebab-case property names:
<button style={{ color: "red", "font-size": "2rem" }}>Click</button>Renders children when when is truthy, otherwise renders fallback:
import { Show } from "solid-js";
// Basic conditional with fallback:
<Show when={isLoggedIn()} fallback={<LoginForm />}>
<Dashboard />
</Show>
// Render function -- receives narrowed non-null accessor:
<Show when={user()}>
{(u) => <p>Welcome, {u().name}</p>}
</Show>
// Keyed -- re-renders when object reference changes:
<Show when={user()} keyed>
<UserProfile user={user()} />
</Show>Behavior: Without keyed, only truthiness changes trigger updates. With keyed, reference changes also trigger re-render.
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 signalindex-- signal, MUST call asindex()to read
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 asitem()to readindex-- direct number, NOT a signal
This is the OPPOSITE of <For>.
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>
// With render function -- receives narrowed value:
<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).
import { Dynamic } from "solid-js/web";
// Dynamic component:
const [currentView, setCurrentView] = createSignal<Component>(HomeView);
<Dynamic component={currentView()} user={currentUser()} />
// Dynamic HTML element:
const [tag, setTag] = createSignal("h1");
<Dynamic component={tag()}>Hello World</Dynamic>
// Polymorphic "as" pattern:
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.
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.
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>| 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 |
- 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
- https://docs.solidjs.com/concepts/understanding-jsx
- https://docs.solidjs.com/reference/components/show
- https://docs.solidjs.com/reference/components/for
- https://docs.solidjs.com/reference/components/index-component
- https://docs.solidjs.com/reference/components/switch-and-match
- https://docs.solidjs.com/reference/components/dynamic
- https://docs.solidjs.com/reference/components/portal
- https://docs.solidjs.com/reference/components/suspense