.claude/skills/solidjs/solid-syntax/solid-syntax-jsx/SKILL.md
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.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer solid-syntax-jsxInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 as index() to readRenders 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 readindex -- direct number, NOT a signalThis 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 |
development
Use when integrating Vite with a backend framework, rendering Vite assets from server-side templates, or setting up dev/production HTML serving. Prevents incorrect manifest.json traversal and missing CSS chunk resolution in production. Covers build.manifest configuration, .vite/manifest.json structure, ManifestChunk properties, dev mode HTML setup, production rendering, CSS/JS chunk resolution, and modulepreload polyfill. Keywords: backend integration, manifest.json, ManifestChunk, Django, Laravel, Rails, modulepreload.
development
Use when encountering dev server startup failures, HMR issues, proxy errors, CORS blocks, or module not found errors during development. Prevents misconfiguring server.hmr behind reverse proxies and forgetting appType: 'custom' in middleware mode. Covers HMR full-reload debugging, proxy configuration, CORS setup, HTTPS certificates, server.fs.strict violations, port conflicts, WebSocket failures, file watcher issues, and middleware mode. Keywords: dev server, HMR, proxy, CORS, HTTPS, WebSocket, port conflict, server.fs.strict, middleware mode, file watcher.
development
Use when encountering pre-bundling errors, dependency resolution failures, stale cache issues, or slow development server startup. Prevents excluding CJS dependencies from pre-bundling (which breaks runtime module resolution) and misconfiguring optimizeDeps. Covers CJS/ESM conversion failures, missing dependency auto-discovery, optimizeDeps configuration, monorepo linked dependencies, cache invalidation, browser cache staleness, and large dependency tree performance. Keywords: pre-bundling, optimizeDeps, CJS, ESM, cache, dependency resolution, monorepo, node_modules/.vite.
development
Use when encountering Vite build failures, chunk size warnings, or version-specific build errors. Prevents the common mistake of using deprecated rollupOptions in v8 or misconfiguring build targets and minifiers. Covers Rolldown/Rollup bundling failures, CSS minification errors, sourcemap problems, library mode build failures, BundleError handling, and asset processing errors. Keywords: build error, Rolldown, chunk size, sourcemap, library mode, minify, BundleError, rollupOptions, build.target.