.claude/skills/solidjs/solid-syntax/solid-syntax-components/SKILL.md
Use when creating components, handling props, working with children, refs, directives, or events in SolidJS. Prevents props destructuring and children access patterns from React that break SolidJS reactivity. Covers Component/ParentComponent/VoidComponent/FlowComponent types, splitProps, mergeProps, children() helper, ref patterns, use: directives, and event delegation. Keywords: splitProps, mergeProps, children, Component, ParentComponent, VoidComponent, FlowComponent, ref, use directive, SolidJS props, event handling.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer solid-syntax-componentsInstall 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.
| Type | Children | Use When |
|------|----------|----------|
| Component<P> | Optional (untyped) | General-purpose component |
| ParentComponent<P> | Required (children: JSX.Element) | Layout wrappers, containers |
| VoidComponent<P> | Forbidden | Icons, inputs, leaf nodes |
| FlowComponent<P, C> | Typed (children: C) | Control flow, render props |
| Need | Tool | Import |
|------|------|--------|
| Access prop values | props.x directly | -- |
| Derived values outside JSX | () => props.x | -- |
| Separate prop groups | splitProps(props, [...keys]) | solid-js |
| Default prop values | mergeProps(defaults, props) | solid-js |
| Spread remaining props | splitProps + {...rest} | solid-js |
| Need | Tool | Import |
|------|------|--------|
| Resolve and cache children | children(() => props.children) | solid-js |
| Iterate resolved children | resolved.toArray() | solid-js |
| Pattern | Syntax | When |
|---------|--------|------|
| Variable assignment | let ref!: HTMLElement | Standard ref |
| Callback | ref={(el) => { ... }} | Side effects on creation |
| Signal ref | const [ref, setRef] = createSignal<HTMLElement>() | Conditional elements |
| Forwarding | Pass ref as regular prop | Parent needs child DOM access |
| System | Syntax | Listener Location | Use When |
|--------|--------|-------------------|----------|
| Delegated | onClick={handler} | document (shared) | Common UI events (23 events) |
| Native | on:scroll={handler} | Element (direct) | Custom events, stopPropagation needed |
NEVER destructure props in function parameters or body -- this severs reactivity. Props are reactive getters on a proxy object. ALWAYS access as props.x.
NEVER access props.children multiple times without the children() helper -- each access can re-create child elements. ALWAYS resolve with children(() => props.children).
NEVER use useRef or forwardRef -- these are React APIs. ALWAYS use let ref!: HTMLElement for refs and pass ref as a regular prop for forwarding.
NEVER use event.stopPropagation() with delegated events expecting it to prevent other delegated handlers -- delegated events share a single document listener. ALWAYS use on: prefix when propagation control is needed.
NEVER call a handler signal directly in an event attribute (onClick={handler()}) -- this evaluates once. ALWAYS wrap in an arrow function: onClick={() => handler()}.
Creating a new component?
|
+-- Does it accept children?
| |
| +-- YES: Are children a specific type (render prop, typed slot)?
| | |
| | +-- YES --> FlowComponent<Props, ChildType>
| | +-- NO --> ParentComponent<Props>
| |
| +-- NO: Will it NEVER have children?
| |
| +-- YES --> VoidComponent<Props>
| +-- MAYBE --> Component<Props>
ALWAYS import types from solid-js:
import type { Component, ParentComponent, VoidComponent, FlowComponent } from "solid-js";
// WRONG -- breaks reactivity (frozen at initial value):
function Greeting({ name }: { name: string }) {
return <h1>Hello {name}</h1>;
}
// WRONG -- same problem:
function Greeting(props: { name: string }) {
const { name } = props;
return <h1>Hello {name}</h1>;
}
// CORRECT -- reactive access:
const Greeting: Component<{ name: string }> = (props) => {
return <h1>Hello {props.name}</h1>;
};
// CORRECT -- derived accessor for use outside JSX:
const Greeting: Component<{ name: string }> = (props) => {
const upper = () => props.name.toUpperCase();
return <h1>Hello {upper()}</h1>;
};
import { splitProps } from "solid-js";
const Button: ParentComponent<ButtonProps> = (props) => {
const [local, styleProps, rest] = splitProps(
props,
["onClick", "disabled"], // Group 1: behavior
["variant", "class"] // Group 2: styling
); // rest: everything else
return (
<button
onClick={local.onClick}
disabled={local.disabled}
class={`btn-${styleProps.variant} ${styleProps.class}`}
{...rest}
>
{props.children}
</button>
);
};
import { mergeProps } from "solid-js";
const Button: ParentComponent<ButtonProps> = (props) => {
const merged = mergeProps(
{ variant: "primary", size: "md", disabled: false },
props
);
// Later sources override earlier ones -- props overrides defaults
return <button class={`btn-${merged.variant}`} disabled={merged.disabled}>{props.children}</button>;
};
// WRONG -- accessing props.children multiple times re-creates elements:
const Bad: ParentComponent = (props) => {
createEffect(() => { console.log(props.children); }); // Re-creates!
return <div>{props.children}</div>; // Creates again!
};
// CORRECT -- resolve once with children() helper:
import { children } from "solid-js";
const Good: ParentComponent = (props) => {
const resolved = children(() => props.children);
createEffect(() => { console.log(resolved()); }); // Stable reference
return <div>{resolved()}</div>;
};
const List: ParentComponent = (props) => {
const resolved = children(() => props.children);
return (
<ul>
<For each={resolved.toArray()}>
{(child) => <li>{child}</li>}
</For>
</ul>
);
};
// WRONG (React pattern):
const ref = useRef<HTMLCanvasElement>(null);
useEffect(() => { ref.current?.getContext("2d"); }, []);
return <canvas ref={ref} />;
// CORRECT (SolidJS):
let canvasRef!: HTMLCanvasElement;
onMount(() => { canvasRef.getContext("2d"); });
return <canvas ref={canvasRef} />;
| Aspect | React useRef | SolidJS ref |
|--------|---------------|---------------|
| Declaration | const ref = useRef(null) | let ref!: HTMLElement |
| Access | ref.current | ref (direct) |
| Timing | After mount (useEffect) | Assigned during render, use in onMount |
| Hook required | Yes (useRef) | No -- plain variable |
| Forwarding | forwardRef() HOC | Pass ref as regular prop |
| Directives | No built-in system | use: prefix for reusable behaviors |
// Parent:
function Parent() {
let childRef!: HTMLCanvasElement;
onMount(() => { childRef.getContext("2d"); });
return <ChildCanvas ref={childRef} />;
}
// Child -- ref is ALWAYS received as a callback, regardless of parent declaration:
const ChildCanvas: Component<{ ref: HTMLCanvasElement | ((el: HTMLCanvasElement) => void) }> = (props) => {
return <canvas ref={props.ref} />;
};
use:)function directiveName(element: Element, accessor: () => any): void;
import { onCleanup } from "solid-js";
function clickOutside(element: Element, accessor: () => () => void): void {
const onClick = (e: Event) => {
if (!element.contains(e.target as Node)) accessor()();
};
document.addEventListener("click", onClick);
onCleanup(() => document.removeEventListener("click", onClick));
}
// TypeScript declaration (required to avoid errors):
declare module "solid-js" {
namespace JSX {
interface Directives {
clickOutside: () => void;
}
}
}
// Usage:
<div use:clickOutside={() => setOpen(false)}>Dropdown content</div>
beforeinput, click, dblclick, contextmenu, focusin, focusout, input, keydown, keyup, mousedown, mousemove, mouseout, mouseover, mouseup, pointerdown, pointermove, pointerout, pointerover, pointerup, touchend, touchmove, touchstart
For ALL other events, ALWAYS use the on: prefix.
const handler = (data: string, event: MouseEvent) => {
console.log("Data:", data, "Target:", event.target);
};
// Avoids creating a new closure -- data is first arg, event is second:
<button onClick={[handler, "hello"]}>Click</button>
stopPropagation() does NOT prevent other delegated handlers. Use on:click when propagation control is needed.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.