solid-js-best-practices

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 c

SKILL.md

GitHub

Solid.js Best Practices

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

Version Routing

Read the project's solid-js version from its manifest and lockfile before applying code examples. If the major is unknown, ask which major the project uses. The existing rule files are based on Solid 1.x; version_basis records the version behind a rule, without limiting its principle to that major. Do not copy a 1.x API example into a 2.x project without checking its current equivalent. Solid 2.x coverage is deferred until its official release. For a pre-release 2.x project, consult the official 1.x migration guide and documentation for the installed release candidate; do not treat these 1.x rules as verified 2.x guidance.

Rules by Category

1. Reactivity (7 rules)

# Rule Priority Description
1-1 Use Signals Correctly CRITICAL Always call signals as functions count() not count
1-2 Use Memo for Derived Values HIGH Use createMemo for computed values, not createEffect
1-3 Effects for Side Effects Only HIGH Use createEffect only for side effects, not derivations
1-7 No Primitives in Reactive Contexts HIGH Don't call hooks or create reactive primitives inside effects or memos
1-4 Avoid Setting Signals in Effects MEDIUM Setting signals in effects can cause infinite loops
1-5 Use Untrack When Needed MEDIUM Use untrack() to prevent unwanted reactive subscriptions
1-6 Batch Signal Updates LOW Use batch() for multiple synchronous signal updates

2. Components (10 rules)

# Rule Priority Description
2-1 Never Destructure Props CRITICAL Destructuring props breaks reactivity
2-6 Components Return Once CRITICAL Never use early returns; use <Show>, <Switch>, etc. in JSX
2-9 Never Call Components as Functions CRITICAL Always use JSX or createComponent(); direct calls leak reactive scope
2-2 Use mergeProps HIGH Use mergeProps for default prop values
2-3 Use splitProps HIGH Use splitProps to separate prop groups safely
2-7 No React-Specific Props HIGH Use class not className, for not htmlFor
2-10 Custom Element TypeScript Declarations HIGH Declare custom element tags in JSX namespace; augment DOM types for newer attributes
2-4 Use children Helper MEDIUM Use children() helper for safe children access
2-5 Prefer Composition MEDIUM Prefer composition and context over prop drilling
2-8 Style Prop Conventions MEDIUM Use object syntax with kebab-case properties for style

3. Control Flow (7 rules)

# Rule Priority Description
3-1 Use Show for Conditionals HIGH Use <Show> for conditional rendering and its function child when callers need a narrowed value
3-2 Use For for Lists HIGH Use <For> for referentially keyed list rendering
3-7 Use keyed for Stateful Children HIGH Add keyed when child has internal state and value identity (not just truthiness) matters
3-3 Use Index for Primitives MEDIUM Use <Index> when array index matters more than identity
3-4 Use Switch/Match MEDIUM Use <Switch>/<Match> for multiple conditions; narrowed accessors throw once their branch deactivates
3-6 Stable Component Mount MEDIUM Avoid rendering the same component in multiple Switch/Show branches
3-5 Provide Fallbacks LOW Always provide fallback props for loading states

4. State Management (7 rules)

# Rule Priority Description
4-1 Signals vs Stores HIGH Use signals for primitives, stores for nested objects
4-2 Use Store Path Syntax HIGH Use path syntax for granular, efficient store updates
4-3 Use produce for Mutations MEDIUM Use produce for complex mutable-style store updates
4-4 Use reconcile for Server Data MEDIUM Use reconcile when integrating server/external data
4-5 Use Context for Global State MEDIUM Use Context API for cross-component shared state
4-6 Store Functions with a Wrapper HIGH Wrap function values so setStore does not invoke them as updater functions
4-7 Cleanup at the Page Ownership Boundary HIGH Use per-page cleanup when multiple routed panes remain mounted; audit every context field when splitting a shared context

5. Refs & DOM (10 rules)

# Rule Priority Description
5-1 Use Refs Correctly HIGH Use callback refs for conditional elements
5-2 Access DOM in onMount HIGH Access DOM elements in onMount, not during render
5-3 Cleanup with onCleanup HIGH Always clean up subscriptions and timers
5-5 Avoid innerHTML HIGH Avoid innerHTML to prevent XSS; use JSX or textContent
5-7 Web Component Controlled State HIGH Use prop:* properties and on:wc-* events for modern custom elements; reserve refs/effects for native or legacy APIs
5-8 Wrap Signal Mutation in View Transition HIGH Call the Solid setter inside startViewTransition() so the browser captures the old DOM before applying the new reactive state
5-9 Two-Way Scroll Sync HIGH Report only settled scroll positions and skip re-scrolling for your own echo
5-10 Possibly Missing progress/meter value HIGH value is always a property and throws on undefined; use attr:value
5-4 Use Directives MEDIUM Use use: directives for reusable element behaviors
5-6 Event Handler Patterns MEDIUM Use on:/oncapture: namespaces and array handler syntax correctly

6. Performance (6 rules)

# Rule Priority Description
6-1 Avoid Unnecessary Tracking HIGH Don't access signals outside reactive contexts
6-2 Use Lazy Components MEDIUM Use lazy() for code splitting large components
6-3 Use Suspense MEDIUM Use <Suspense> for async loading boundaries
6-6 Web Component CSS and Bundle Strategy MEDIUM Import components individually; place ::part() overrides in a global stylesheet
6-4 Optimize Store Access LOW Access only the store properties you need
6-5 Prefer classList LOW Use classList prop for conditional class toggling

7. Accessibility (4 rules)

# Rule Priority Description
7-1 Use Semantic HTML HIGH Use appropriate semantic HTML elements
7-2 Use ARIA Attributes MEDIUM Apply appropriate ARIA attributes for custom controls
7-3 Support Keyboard Navigation MEDIUM Ensure all interactive elements are keyboard accessible
7-4 End-Match Root Router Links HIGH Add end matching so the root link is not current on every route

8. Testing (12 rules)

# Rule Priority Description
8-1 Configure Vitest for Solid CRITICAL Configure Vitest with Solid-specific resolve conditions and plugin
8-2 Wrap Render in Arrow Functions CRITICAL Always use render(() => <C />) not render(<C />)
8-3 Test Primitives in a Root HIGH Wrap signal/effect/memo tests in createRoot or renderHook
8-4 Handle Async in Tests HIGH Use findBy queries and proper timer config for async behavior
8-5 Use Accessible Queries MEDIUM Prefer role and label queries over test IDs
8-6 Separate Logic from UI Tests MEDIUM Test primitives/hooks independently from component rendering
8-7 Browser Mode for Browser-Dependent Behavior HIGH Use a real browser for layout, focus, native APIs, and custom-element behavior jsdom cannot model faithfully
8-8 Testing Headless UI Libraries with Non-Standard ARIA MEDIUM Headless UI libraries use non-obvious ARIA structures and portals; inspect the actual tree before querying
8-9 Browser-Native API Test Isolation HIGH Clear IndexedDB and localStorage between tests; close connection before deleteDatabase
8-10 Router Integration Testing HIGH Use MemoryRouter root prop to provide router context to layout providers
8-11 TanStack Query Test Setup HIGH Create a fresh QueryClient per test with retry and caching disabled
8-12 Deproxy Before Structured Clone HIGH Remove every reactive proxy before writing data to IndexedDB

9. Web Component Integration (8 rules)

# Rule Priority Description
9-1 Register Custom Elements at App Entry HIGH Import /define side-effects before any SolidJS reactive context
9-2 Handle slotchange After Slot Assignment HIGH slotchange is delivered in a microtask; defer only work that needs a later lifecycle or rendering phase
9-3 Treat Custom Element and SolidJS Reactivity as Decoupled MEDIUM Use one-way data flow (SolidJS -> attributes/props -> events -> SolidJS); never read custom element internal state from SolidJS reactive contexts
9-4 Thin Web Component Wrappers HIGH Wrappers own labels, layout, type adaptation, and form glue; custom elements own timing and native sync
9-5 Property vs Attribute Binding HIGH Dynamic props on custom elements are already properties; use prop:* for intent and attr:* for data-*, ID-reference ARIA, and CSS-visible attributes
9-6 Register Custom Fields with Form Libraries HIGH Ensure property-bound custom fields enter lazy form-library registries
9-7 Store State for Web-Component-Heavy Forms MEDIUM Prefer a Solid store when custom elements already own field interaction
9-8 Wrapper Signal for Controlled-Only WC HIGH When a WC has no defaultValue, use createSignal in the wrapper to own the uncontrolled → controlled bridge

Task-Based Rule Selection

Writing New Components

Load these rules when creating new Solid.js components:

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

Web Component Integration

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

Rule Why
9-1 Register before any SolidJS context mounts
9-2 Handle assigned nodes after the browser delivers slotchange
9-3 One-way data flow design
9-4 Keep wrappers focused on app concerns
9-5 prop:* for properties, attr:* for attributes CSS or ARIA must see
9-6 Property-bound fields in form-library registries
9-7 Store state for custom-element-heavy forms
5-6 Use on: namespace for custom element events

Code Review

Focus on these rules during code review:

Priority Rules
CRITICAL 1-1, 2-1, 2-6, 2-9
HIGH 1-2, 1-3, 1-7, 2-7, 5-2, 5-3, 5-5

Performance Optimization

Load these rules when optimizing performance:

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

State Management

Load these rules when working with application state:

Rule Focus
4-1 Choose the right primitive
4-2 Efficient updates
4-3 Complex mutations
4-4 External data integration
4-5 Cross-component state
4-6 Function values in stores
4-7 Several mounted panes sharing context

Accessibility Audit

Load these rules when auditing accessibility:

Rule Focus
7-1 Semantic structure
7-2 Screen reader support
7-3 Keyboard users
7-4 Current-page state on router links

Writing Tests

Load these rules when writing or reviewing tests:

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

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:

Rule Why
2-10 Declare custom element tags in JSX namespace; type newer HTML attributes and experimental CSS properties
5-6 Use on: for all custom element events; type CustomEvent payloads correctly
5-7 Prefer declarative prop:*/on:wc-*; use refs for native or legacy APIs only
6-6 Per-component imports for tree-shaking; ::part() overrides in global CSS only
9-8 When the WC has no defaultValue, own the uncontrolled → controlled bridge with createSignal
5-8 Keep Solid's DOM update inside the View Transition snapshot callback
5-10 value on native <progress>/<meter> throws on undefined

Carousels and Scroll-Synced Components

Load 5-9 when an index is both driven into a scroll container and reported back out of it.

Migrating From React

Load signals, component execution, reactive props, HTML prop names, style syntax, control flow, list rendering, and event handlers.

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