agentsclimarketplace

Solid js best practices

Skill richardcarls/solid-js-best-practices

solid-js-best-practices enables comprehensive best practices for building Solid.js applications and components. It is optimized for AI-assisted code generation, review, refactoring, and web component integration.

Install
npx -y skills add richardcarls/solid-js-best-practices

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Solid.js best practices for AI-assisted code generation, code review, refactoring, and debugging reactivity issues. Use when working in any SolidJS project or codebase — writing components, auditing code, migrating from React, fixing signals and fine-grained reactivity bugs, or integrating web component libraries. 67 rules across 9 categories (reactivity, components, control flow, state management, refs/DOM, performance, accessibility, testing, web component integration) ranked by priority.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

27.4 KB, as published. Nobody here has run it

Solid.js Best Practices

Comprehensive best practices for building Solid.js applications and components, optimized for AI-assisted code generation, review, and refactoring.

Quick Reference

Essential Imports

import {
  createSignal,
  createEffect,
  createMemo,
  createResource,
  onMount,
  onCleanup,
  Show,
  For,
  Switch,
  Match,
  Index,
  Suspense,
  ErrorBoundary,
  lazy,
  batch,
  untrack,
  mergeProps,
  splitProps,
  children,
} from "solid-js";

import { createStore, produce, reconcile } from "solid-js/store";

Component Skeleton

import { Component, JSX, mergeProps, splitProps } from "solid-js";

interface MyComponentProps {
  title: string;
  count?: number;
  onAction?: () => void;
  children?: JSX.Element;
}

const MyComponent: Component<MyComponentProps> = (props) => {
  // Merge default props
  const merged = mergeProps({ count: 0 }, props);

  // Split component props from passed-through props
  const [local, others] = splitProps(merged, ["title", "count", "onAction"]);

  // Local reactive state
  const [value, setValue] = createSignal("");

  // Derived/computed values
  const doubled = createMemo(() => local.count * 2);

  // Side effects
  createEffect(() => {
    console.log("Count changed:", local.count);
  });

  // Lifecycle
  onMount(() => {
    console.log("Component mounted");
  });

  onCleanup(() => {
    console.log("Component cleanup");
  });

  return (
    <div {...others}>
      <h1>{local.title}</h1>
      <p>Count: {local.count}, Doubled: {doubled()}</p>
      <input
        value={value()}
        onInput={(e) => setValue(e.currentTarget.value)}
      />
      <button onClick={local.onAction}>Action</button>
      {props.children}
    </div>
  );
};

export default MyComponent;

Rules by Category

1. Reactivity (7 rules)

#RulePriorityDescription
1-1Use Signals CorrectlyCRITICALAlways call signals as functions count() not count
1-2Use Memo for Derived ValuesHIGHUse createMemo for computed values, not createEffect
1-3Effects for Side Effects OnlyHIGHUse createEffect only for side effects, not derivations
1-7No Primitives in Reactive ContextsHIGHDon't call hooks or create reactive primitives inside effects or memos
1-4Avoid Setting Signals in EffectsMEDIUMSetting signals in effects can cause infinite loops
1-5Use Untrack When NeededMEDIUMUse untrack() to prevent unwanted reactive subscriptions
1-6Batch Signal UpdatesLOWUse batch() for multiple synchronous signal updates

2. Components (10 rules)

#RulePriorityDescription
2-1Never Destructure PropsCRITICALDestructuring props breaks reactivity
2-6Components Return OnceCRITICALNever use early returns — use <Show>, <Switch>, etc. in JSX
2-9Never Call Components as FunctionsCRITICALAlways use JSX or createComponent() — direct calls leak reactive scope
2-2Use mergePropsHIGHUse mergeProps for default prop values
2-3Use splitPropsHIGHUse splitProps to separate prop groups safely
2-7No React-Specific PropsHIGHUse class not className, for not htmlFor
2-10Custom Element TypeScript DeclarationsHIGHDeclare custom element tags in JSX namespace; augment DOM types for newer attributes
2-4Use children HelperMEDIUMUse children() helper for safe children access
2-5Prefer CompositionMEDIUMPrefer composition and context over prop drilling
2-8Style Prop ConventionsMEDIUMUse object syntax with kebab-case properties for style

3. Control Flow (7 rules)

#RulePriorityDescription
3-1Use Show for ConditionalsHIGHUse <Show> instead of ternary operators
3-2Use For for ListsHIGHUse <For> for referentially-keyed list rendering
3-7Use keyed for Stateful ChildrenHIGHAdd keyed when child has internal state and value identity (not just truthiness) matters
3-3Use Index for PrimitivesMEDIUMUse <Index> when array index matters more than identity
3-4Use Switch/MatchMEDIUMUse <Switch>/<Match> for multiple conditions; prefer <Show> for single gates
3-6Stable Component MountMEDIUMAvoid rendering the same component in multiple Switch/Show branches
3-5Provide FallbacksLOWAlways provide fallback props for loading states

4. State Management (7 rules)

#RulePriorityDescription
4-1Signals vs StoresHIGHUse signals for primitives, stores for nested objects
4-2Use Store Path SyntaxHIGHUse path syntax for granular, efficient store updates
4-3Use produce for MutationsMEDIUMUse produce for complex mutable-style store updates
4-4Use reconcile for Server DataMEDIUMUse reconcile when integrating server/external data
4-5Use Context for Global StateMEDIUMUse Context API for cross-component shared state
4-6Store Functions with a WrapperHIGHWrap function values so setStore does not invoke them as updater functions
4-7Cleanup at the Page Ownership BoundaryHIGHUse per-page cleanup when multiple routed panes remain mounted

5. Refs & DOM (7 rules)

#RulePriorityDescription
5-1Use Refs CorrectlyHIGHUse callback refs for conditional elements
5-2Access DOM in onMountHIGHAccess DOM elements in onMount, not during render
5-3Cleanup with onCleanupHIGHAlways clean up subscriptions and timers
5-5Avoid innerHTMLHIGHAvoid innerHTML to prevent XSS — use JSX or textContent
5-7Web Component Controlled StateHIGHUse prop:* properties and on:wc-* events for modern custom elements; reserve refs/effects for native or legacy APIs
5-4Use DirectivesMEDIUMUse use: directives for reusable element behaviors
5-6Event Handler PatternsMEDIUMUse on:/oncapture: namespaces and array handler syntax correctly

6. Performance (6 rules)

#RulePriorityDescription
6-1Avoid Unnecessary TrackingHIGHDon't access signals outside reactive contexts
6-2Use Lazy ComponentsMEDIUMUse lazy() for code splitting large components
6-3Use SuspenseMEDIUMUse <Suspense> for async loading boundaries
6-6Web Component CSS and Bundle StrategyMEDIUMImport components individually; place ::part() overrides in a global stylesheet
6-4Optimize Store AccessLOWAccess only the store properties you need
6-5Prefer classListLOWUse classList prop for conditional class toggling

7. Accessibility (4 rules)

#RulePriorityDescription
7-1Use Semantic HTMLHIGHUse appropriate semantic HTML elements
7-2Use ARIA AttributesMEDIUMApply appropriate ARIA attributes for custom controls
7-3Support Keyboard NavigationMEDIUMEnsure all interactive elements are keyboard accessible
7-4End-Match Root Router LinksHIGHAdd end matching so the root link is not current on every route

8. Testing (12 rules)

#RulePriorityDescription
8-1Configure Vitest for SolidCRITICALConfigure Vitest with Solid-specific resolve conditions and plugin
8-2Wrap Render in Arrow FunctionsCRITICALAlways use render(() => <C />) not render(<C />)
8-3Test Primitives in a RootHIGHWrap signal/effect/memo tests in createRoot or renderHook
8-4Handle Async in TestsHIGHUse findBy queries and proper timer config for async behavior
8-5Use Accessible QueriesMEDIUMPrefer role and label queries over test IDs
8-6Separate Logic from UI TestsMEDIUMTest primitives/hooks independently from component rendering
8-7Browser Mode for Web Components and PWA APIsHIGHUse Vitest browser mode (real Chromium) for custom elements, shadow DOM, and browser-native APIs
8-8Testing Headless UI Libraries with Non-Standard ARIAMEDIUMHeadless UI libraries use non-obvious ARIA structures and portals — inspect the actual tree before querying
8-9Browser-Native API Test IsolationHIGHClear IndexedDB and localStorage between tests — close connection before deleteDatabase
8-10Router Integration TestingHIGHUse MemoryRouter root prop to provide router context to layout providers
8-11TanStack Query Test SetupHIGHCreate a fresh QueryClient per test with retry and caching disabled
8-12Deproxy Before Structured CloneHIGHRemove every reactive proxy before writing data to IndexedDB

9. Web Component Integration (7 rules)

#RulePriorityDescription
9-1Register Custom Elements at App EntryHIGHImport /define side-effects before any SolidJS reactive context
9-2Defer slotchange Handler Side EffectsHIGHAlways defer focus, state writes, and DOM mutations in slotchange via queueMicrotask
9-3Treat Custom Element and SolidJS Reactivity as DecoupledMEDIUMUse one-way data flow (SolidJS -> attributes/props -> events -> SolidJS); never read custom element internal state from SolidJS reactive contexts
9-4Thin Web Component WrappersHIGHWrappers own labels, layout, type adaptation, and form glue; custom elements own timing and native sync
9-5Property vs Attribute BindingHIGHUse prop:* for controlled state and rich data; use attributes only for appropriate primitives
9-6Register Custom Fields with Form LibrariesHIGHEnsure property-bound custom fields enter lazy form-library registries
9-7Store State for Web-Component-Heavy FormsMEDIUMPrefer a Solid store when custom elements already own field interaction

Task-Based Rule Selection

Writing New Components

Load these rules when creating new Solid.js components:

RuleWhy
1-1Ensure signals are called as functions
2-1Prevent reactivity breakage
2-6No early returns — use control flow in JSX
2-9Never call components as plain functions
2-2Handle default props correctly
2-3Separate local and forwarded props
3-1Proper conditional rendering
3-7keyed for forms and stateful children
3-2Efficient list rendering
5-3Prevent memory leaks

Web Component Integration

Load these rules when integrating Lit or other custom elements with SolidJS:

RuleWhy
9-1Register before any SolidJS context mounts
9-2Prevent synchronous side effects inside runUpdates
9-3One-way data flow design
9-4Keep wrappers focused on app concerns
9-5Bind JS properties with prop:*
5-6Use on: namespace for custom element events

Code Review

Focus on these rules during code review:

PriorityRules
CRITICAL1-1, 2-1, 2-6, 2-9
HIGH1-2, 1-3, 1-7, 2-7, 5-2, 5-3, 5-5

Performance Optimization

Load these rules when optimizing performance:

RuleFocus
1-2Prevent unnecessary recomputation
1-6Reduce update cycles
4-2Granular store updates
6-1Prevent unwanted subscriptions
6-2Code splitting
6-4Efficient store access

State Management

Load these rules when working with application state:

RuleFocus
4-1Choose the right primitive
4-2Efficient updates
4-3Complex mutations
4-4External data integration
4-5Cross-component state

Accessibility Audit

Load these rules when auditing accessibility:

RuleFocus
7-1Semantic structure
7-2Screen reader support
7-3Keyboard users

Writing Tests

Load these rules when writing or reviewing tests:

RuleFocus
8-1Correct Vitest configuration
8-2Reactive render scope
8-3Reactive ownership for primitives
8-4Async queries and timers
8-5Accessible query selection
8-6Test architecture
8-7When to use browser mode vs jsdom
8-8Portals and non-standard ARIA structures
8-9IDB and localStorage cleanup patterns
8-10MemoryRouter setup for integration tests
8-11QueryClient configuration for tests

Integrating Web Components / Custom Elements

Load these rules when using any custom element library (Shoelace, FAST, Lion, Material Web Components, etc.) or native browser APIs like <dialog> and the Popover API:

RuleWhy
2-10Declare custom element tags in JSX namespace; type newer HTML attributes and experimental CSS properties
5-6Use on: for all custom element events; type CustomEvent payloads correctly
5-7Prefer declarative prop:*/on:wc-*; use refs for native or legacy APIs only
6-6Per-component imports for tree-shaking; ::part() overrides in global CSS only

Common Mistakes to Catch

MistakeRuleSolution
Forgetting () on signal access1-1Always call signals: count()
Destructuring props2-1Access via props.name
Using ternaries for conditionals3-1Use <Show> component
.map() for lists3-2Use <For> component
Deriving values in effects1-2Use createMemo
Setting signals in effects1-4Use createMemo or external triggers
Accessing DOM during render5-2Use onMount
Forgetting cleanup5-3Use onCleanup
Early returns in components2-6Use <Show>, <Switch> in JSX instead
Using className or htmlFor2-7Use class and for (standard HTML)
style="color: red" or camelCase styles2-8Use style={{ color: "red" }} with kebab-case
Using innerHTML with user data5-5Use JSX or sanitize with DOMPurify
Spreading whole store6-4Access specific properties
String concatenation for class toggling6-5Use classList={{ active: isActive() }}
render(<Comp />) without arrow8-2Use render(() => <Comp />)
Effects in tests without owner8-3Wrap in createRoot or use renderHook
getBy for async content8-4Use findBy queries
MyComp(props) instead of <MyComp />2-9Always use JSX syntax or createComponent()
Calling useMatch()/useQuery() inside createEffect/createComputed1-7Call hooks once at component init, not inside reactive computations
Same component in Switch fallback and Match branch3-6Keep component in one stable position; use CSS for layout changes
Custom elements don't upgrade / lifecycle doesn't fire in tests8-7Use Vitest browser mode (real Chromium) instead of jsdom
IDB state persists between tests causing order-dependent failures8-9Close connection before deleteDatabase; use useCleanDb()
Router primitives throw "can only be used inside a Route"8-10Use MemoryRouter root prop with a layout factory
QueryClient retries mask errors / cache leaks between tests8-11Use makeTestQueryClient() with retry: false, gcTime: 0
waitFor(length === 0) passes before data loads8-4Use a settled anchor with findBy before asserting absence
getByRole('form') throws even though the form exists7-2Add aria-label or aria-labelledby to expose role="form"
<my-element onMyChange={...}> misses all events5-6Use on:my-changeon: prefix required for all web component custom events
my-element::part(...) rule inside a .module.css is silently ignored6-6Move ::part() overrides to a non-module global stylesheet
Barrel import of entire web component library6-6Import individual components by path to enable tree-shaking
`prop:value missing on custom element controlled state5-7Use prop:value={signal()} plus on:wc-*-change
<div popover> or <button popoverTarget="x"> TypeScript error2-10Augment HTMLElement / HTMLButtonElement in a .d.ts file
Object/array prop on custom element becomes "[object Object]"9-5Use prop:options={options()} or another prop:* binding
Experimental CSS property (anchor-name) produces a TypeScript error2-8Cast with as unknown as JSX.CSSProperties instead of as never
<Show when={record}> without keyed for a form component3-7Add keyed — without it, switching records silently reuses the old form state
<Switch><Match> for a single condition gating one heavy component3-4Use <Show> — Switch creates 2N+4 memos vs Show's 3
batch() inside createEffect or reactive context1-6batch() is a no-op inside runUpdates — only use at top-level handlers
Custom element slotchange handler calling .focus() or writing state synchronously9-2Defer all side effects via queueMicrotask — fires inside runUpdates on second+ mount
Custom element registered inside a component or lazy chunk9-1Import /define side-effects at app entry before any SolidJS rendering
Reading custom element internal state (for example el.open or el.value) from createEffect9-3Element properties are not Solid signals; use on:wc-* events to propagate changes upward

Solid.js vs React Mental Model

When helping users familiar with React, keep these differences in mind:

ReactSolid.js
Components re-render on state changeComponents run once, signals update DOM directly
useState returns [value, setter]createSignal returns [getter, setter]
useMemo with deps arraycreateMemo with automatic tracking
useEffect(fn, [deps])createEffect(fn) (no deps array — automatic tracking)
Destructure props freelyNever destructure props
Early returns (if (!x) return null)<Show> / <Switch> in JSX (components return once)
{condition && <Component />}<Show when={condition}><Component /></Show>
{items.map(item => ...)}<For each={items}>{item => ...}</For>
classNameclass
htmlForfor
style={{ fontSize: 14 }}style={{ "font-size": "14px" }}
Context requires useContext hookContext works with useContext or direct access
React 18: ref + addEventListener for custom element events; React 19: onMyEvent={handler} nativelyon:my-event={handler} — always use on: prefix with web component events

Priority Levels

  • CRITICAL: Fix immediately. Causes bugs, broken reactivity, or runtime errors.
  • HIGH: Address in code reviews. Important for correctness and maintainability.
  • MEDIUM: Apply when relevant. Improves code quality and performance.
  • LOW: Consider during refactoring. Nice-to-have optimizations.

Key Solid.js Concepts

Fine-Grained Reactivity

Solid.js updates only the specific DOM elements that depend on changed data, not entire component trees. This is achieved through:

  • Signals: Reactive primitives that track dependencies
  • Effects: Side effects that automatically re-run when dependencies change
  • Memos: Cached derived values that only recompute when dependencies change

Components Render Once

Unlike React, Solid components are functions that run once during initial render. Reactivity happens at the signal level, not the component level. This is why:

  • Props must not be destructured (would capture static values)
  • Signals must be called as functions (to maintain reactive tracking)
  • Control flow uses special components (<Show>, <For>) instead of JS expressions

Stores for Complex State

For nested objects and arrays, Solid provides stores with:

  • Fine-grained updates via path syntax
  • Automatic proxy wrapping for nested reactivity
  • Utilities like produce and reconcile for common patterns

Tooling

For automated linting alongside these best practices, use eslint-plugin-solid. The plugin catches many of the same issues this skill covers (destructured props, early returns, React-specific props, innerHTML usage, style prop format, etc.) and provides auto-fixable rules.

Resources

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.