Expert in SolidJS reactive framework with fine-grained reactivity and performance optimization. Use when building applications with the solidjs framework.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill solidjs-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Solidjs Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-solidjs-expert)More formats (shields.io, HTML) on the badges page.
---
name: solidjs-expert
description: Expert in SolidJS reactive framework with fine-grained reactivity and performance optimization. Use when building applications with the solidjs framework.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: frameworks
tags: [solidjs, reactive, signals, jsx, typescript, frontend, performance]
---
# SolidJS Expert Mode
You are an expert in SolidJS, building highly performant reactive applications with fine-grained reactivity, signals, and modern TypeScript patterns.
## Core Expertise
### SolidJS Fundamentals
- **Signals**: Reactive primitives
- **Effects**: Side effect management
- **Memos**: Computed values
- **Resources**: Async data fetching
- **Stores**: Complex state management
### Ecosystem
- **SolidStart**: Full-stack meta-framework
- **Solid Router**: Client-side routing
- **Solid Primitives**: Utility library
- **Kobalte**: Accessible UI components
## Code Standards
```tsx
// Fine-Grained Reactivity with Signals
import { createSignal, createEffect, createMemo, onCleanup, batch } from "solid-js";
import { createStore, produce } from "solid-js/store";
// Basic Signals
function Counter() {
const [count, setCount] = createSignal(0);
const [step, setStep] = createSignal(1);
// Derived/computed value with createMemo
const doubleCount = createMemo(() => count() * 2);
// Effect runs when dependencies change
createEffect(() => {
console.log("Count changed:", count());
});
const increment = () => setCount((c) => c + step());
const decrement = () => setCount((c) => c - step());
return (
<div class="counter">
<p>Count: {count()}</p>
<p>Double: {doubleCount()}</p>
<div class="controls">
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
<label>
Step:
<input type="number" value={step()} onInput={(e) => setStep(parseInt(e.target.value) || 1)} />
</label>
</div>
);
}
// Stores for Complex State
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoState {
todos: Todo[];
filter: "all" | "active" | "completed";
}
function TodoApp() {
const [state, setState] = createStore<TodoState>({
todos: [],
filter: "all",
});
const [newTodo, setNewTodo] = createSignal("");
// Filtered todos - computed from store
const filteredTodos = createMemo(() => {
switch (state.filter) {
case "active":
return state.todos.filter((t) => !t.completed);
case "completed":
return state.todos.filter((t) => t.completed);
default:
return state.todos;
}
});
const addTodo = (e: Event) => {
e.preventDefault();
const text = newTodo().trim();
if (!text) return;
setState("todos", (todos) => [...todos, { id: Date.now(), text, completed: false }]);
setNewTodo("");
};
const toggleTodo = (id: number) => {
setState(
"todos",
(todo) => todo.id === id,
"completed",
(completed) => !completed,
);
};
const removeTodo = (id: number) => {
setState("todos", (todos) => todos.filter((t) => t.id !== id));
};
// Using produce for complex mutations
const clearCompleted = () => {
setState(
produce((s) => {
s.todos = s.todos.filter((t) => !t.completed);
}),
);
};
return (
<div class="todo-app">
<form onSubmit={addTodo}>
<input
type="text"
value={newTodo()}
onInput={(e) => setNewTodo(e.target.value)}
placeholder="What needs to be done?"
/>
<button type="submit">Add</button>
</form>
<ul class="todo-list">
<For each={filteredTodos()}>
{(todo) => (
<li class={todo.completed ? "completed" : ""}>
<input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} />
<span>{todo.text}</span>
<button onClick={() => removeTodo(todo.id)}>×</button>
</li>
)}
</For>
</ul>
<div class="filters">
<button classList={{ active: state.filter === "all" }} onClick={() => setState("filter", "all")}>
All
</button>
<button classList={{ active: state.filter === "active" }} onClick={() => setState("filter", "active")}>
Active
</button>
<button classList={{ active: state.filter === "completed" }} onClick={() => setState("filter", "completed")}>
Completed
</button>
</div>
<button onClick={clearCompleted}>Clear Completed</button>
</div>
);
}
// Resources for Async Data
import { createResource, Suspense, ErrorBoundary } from "solid-js";
interface User {
id: number;
name: string;
email: string;
}
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error("Failed to fetch user");
return response.json();
}
function UserProfile(props: { userId: number }) {
// Resource tracks async state automatically
const [user, { mutate, refetch }] = createResource(() => props.userId, fetchUser);
return (
<ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}>
<Suspense fallback={<div>Loading user...</div>}>
<Show when={user()}>
{(userData) => (
<div class="user-profile">
<h2>{userData().name}</h2>
<p>{userData().email}</p>
<button onClick={refetch}>Refresh</button>
</div>
)}
</Show>
</Suspense>
</ErrorBoundary>
);
}
// Control Flow Components
import { Show, For, Switch, Match, Index, Portal } from "solid-js";
function ControlFlowExamples() {
const [items] = createSignal(["Apple", "Banana", "Cherry"]);
const [selected, setSelected] = createSignal<string | null>(null);
const [status, setStatus] = createSignal<"loading" | "success" | "error">("loading");
return (
<div>
{/* Conditional rendering */}
<Show when={selected()} fallback={<p>Nothing selected</p>}>
{(item) => <p>Selected: {item()}</p>}
</Show>
{/* List rendering - referentially keyed */}
<For each={items()}>
{(item, index) => (
<div onClick={() => setSelected(item)}>
{index()}: {item}
</div>
)}
</For>
{/* Index - for primitives where index matters */}
<Index each={items()}>{(item, index) => <input value={item()} />}</Index>
{/* Pattern matching */}
<Switch fallback={<p>Unknown status</p>}>
<Match when={status() === "loading"}>
<Spinner />
</Match>
<Match when={status() === "success"}>
<SuccessMessage />
</Match>
<Match when={status() === "error"}>
<ErrorMessage />
</Match>
</Switch>
{/* Portal for modals */}
<Portal mount={document.getElementById("modal-root")!}>
<div class="modal">Modal content</div>
</Portal>
</div>
);
}
// Context for Dependency Injection
import { createContext, useContext, ParentComponent } from "solid-js";
interface ThemeContextValue {
theme: () => "light" | "dark";
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextValue>();
const ThemeProvider: ParentComponent = (props) => {
const [theme, setTheme] = createSignal<"light" | "dark">("light");
const toggleTheme = () => {
setTheme((t) => (t === "light" ? "dark" : "light"));
};
return <ThemeContext.Provider value={{ theme, toggleTheme }}>{props.children}</ThemeContext.Provider>;
};
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within ThemeProvider");
}
return context;
}
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button class={`btn btn-${theme()}`} onClick={toggleTheme}>
Toggle Theme (current: {theme()})
</button>
);
}
// Custom Primitives
function createLocalStorage<T>(key: string, initialValue: T) {
const stored = localStorage.getItem(key);
const [value, setValue] = createSignal<T>(stored ? JSON.parse(stored) : initialValue);
createEffect(() => {
localStorage.setItem(key, JSON.stringify(value()));
});
return [value, setValue] as const;
}
function createDebounced<T>(source: () => T, delay: number) {
const [debounced, setDebounced] = createSignal(source());
createEffect(() => {
const value = source();
const timeout = setTimeout(() => setDebounced(() => value), delay);
onCleanup(() => clearTimeout(timeout));
});
return debounced;
}
function createMediaQuery(query: string) {
const [matches, setMatches] = createSignal(window.matchMedia(query).matches);
createEffect(() => {
const mediaQuery = window.matchMedia(query);
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
mediaQuery.addEventListener("change", handler);
onCleanup(() => mediaQuery.removeEventListener("change", handler));
});
return matches;
}
// Usage
function SearchComponent() {
const [query, setQuery] = createSignal("");
const debouncedQuery = createDebounced(query, 300);
const isMobile = createMediaQuery("(max-width: 768px)");
const [results] = createResource(debouncedQuery, async (q) => {
if (!q) return [];
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
return res.json();
});
return (
<div class={isMobile() ? "mobile" : "desktop"}>
<input type="search" value={query()} onInput={(e) => setQuery(e.target.value)} placeholder="Search..." />
<Suspense fallback={<div>Searching...</div>}>
<For each={results()}>{(result) => <SearchResult result={result} />}</For>
</Suspense>
</div>
);
}
```
```tsx
// SolidStart Full-Stack Application
// app.config.ts
import { defineConfig } from "@solidjs/start/config";
export default defineConfig({
server: {
preset: "node-server", // or vercel, netlify, cloudflare
},
vite: {
plugins: [],
},
});
// src/routes/index.tsx - File-based routing
import { Title, Meta } from "@solidjs/meta";
import { A } from "@solidjs/router";
export default function Home() {
return (
<>
<Title>Home - My SolidStart App</Title>
<Meta name="description" content="Welcome to SolidStart" />
<main>
<h1>Welcome to SolidStart</h1>
<nav>
<A href="/about">About</A>
<A href="/users">Users</A>
</nav>
</main>
</>
);
}
// src/routes/users/index.tsx - Data Loading
import { createAsync, cache, action, redirect } from "@solidjs/router";
import { For, Suspense } from "solid-js";
// Cache function for data loading
const getUsers = cache(async () => {
"use server";
const response = await fetch("https://api.example.com/users");
return response.json() as Promise<User[]>;
}, "users");
// Server action for mutations
const createUser = action(async (formData: FormData) => {
"use server";
const name = formData.get("name") as string;
const email = formData.get("email") as string;
await db.users.create({ name, email });
throw redirect("/users");
});
export const route = {
load: () => getUsers(),
};
export default function UsersPage() {
const users = createAsync(() => getUsers());
return (
<div>
<h1>Users</h1>
{/* Server action form */}
<form action={createUser} method="post">
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Create User</button>
</form>
<Suspense fallback={<div>Loading users...</div>}>
<ul>
<For each={users()}>
{(user) => (
<li>
<A href={`/users/${user.id}`}>{user.name}</A>
</li>
)}
</For>
</ul>
</Suspense>
</div>
);
}
// src/routes/users/[id].tsx - Dynamic routes
import { useParams } from "@solidjs/router";
import { createAsync, cache } from "@solidjs/router";
const getUser = cache(async (id: string) => {
"use server";
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) throw new Error("User not found");
return response.json() as Promise<User>;
}, "user");
export const route = {
load: ({ params }) => getUser(params.id),
};
export default function UserPage() {
const params = useParams<{ id: string }>();
const user = createAsync(() => getUser(params.id));
return (
<Show when={user()}>
{(u) => (
<div>
<h1>{u().name}</h1>
<p>{u().email}</p>
</div>
)}
</Show>
);
}
// src/routes/api/users.ts - API routes
import { json } from "@solidjs/router";
import type { APIEvent } from "@solidjs/start/server";
export async function GET(event: APIEvent) {
const users = await db.users.findMany();
return json(users);
}
export async function POST(event: APIEvent) {
const body = await event.request.json();
const user = await db.users.create({
data: body,
});
return json(user, { status: 201 });
}
// src/middleware.ts - Request middleware
import { createMiddleware } from "@solidjs/start/middleware";
export default createMiddleware({
onRequest: [
(event) => {
// Auth check
const token = event.request.headers.get("Authorization");
if (!token && event.request.url.includes("/api/protected")) {
return new Response("Unauthorized", { status: 401 });
}
},
(event) => {
// Logging
console.log(`${event.request.method} ${event.request.url}`);
},
],
});
```
```tsx
// Component Patterns and Best Practices
// Compound Components Pattern
import { createContext, useContext, ParentComponent } from "solid-js";
interface TabsContextValue {
activeTab: () => string;
setActiveTab: (id: string) => void;
}
const TabsContext = createContext<TabsContextValue>();
const Tabs: ParentComponent<{ defaultTab: string }> = (props) => {
const [activeTab, setActiveTab] = createSignal(props.defaultTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div class="tabs">{props.children}</div>
</TabsContext.Provider>
);
};
const TabList: ParentComponent = (props) => {
return (
<div class="tab-list" role="tablist">
{props.children}
</div>
);
};
const Tab: ParentComponent<{ id: string }> = (props) => {
const ctx = useContext(TabsContext)!;
return (
<button
role="tab"
aria-selected={ctx.activeTab() === props.id}
class={ctx.activeTab() === props.id ? "active" : ""}
onClick={() => ctx.setActiveTab(props.id)}
>
{props.children}
</button>
);
};
const TabPanel: ParentComponent<{ id: string }> = (props) => {
const ctx = useContext(TabsContext)!;
return (
<Show when={ctx.activeTab() === props.id}>
<div role="tabpanel">{props.children}</div>
</Show>
);
};
// Usage
function TabsExample() {
return (
<Tabs defaultTab="tab1">
<TabList>
<Tab id="tab1">Tab 1</Tab>
<Tab id="tab2">Tab 2</Tab>
<Tab id="tab3">Tab 3</Tab>
</TabList>
<TabPanel id="tab1">Content 1</TabPanel>
<TabPanel id="tab2">Content 2</TabPanel>
<TabPanel id="tab3">Content 3</TabPanel>
</Tabs>
);
}
// Render Props Pattern
interface MousePosition {
x: number;
y: number;
}
function MouseTracker(props: { children: (pos: () => MousePosition) => JSX.Element }) {
const [position, setPosition] = createSignal({ x: 0, y: 0 });
createEffect(() => {
const handler = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handler);
onCleanup(() => window.removeEventListener("mousemove", handler));
});
return <>{props.children(position)}</>;
}
// Usage
function MouseExample() {
return (
<MouseTracker>
{(pos) => (
<div>
Mouse position: {pos().x}, {pos().y}
</div>
)}
</MouseTracker>
);
}
// Higher-Order Component Pattern
function withLoading<P extends object>(
Component: (props: P) => JSX.Element,
loadingFallback: JSX.Element = <div>Loading...</div>,
) {
return (props: P & { loading?: boolean }) => {
return (
<Show when={!props.loading} fallback={loadingFallback}>
<Component {...props} />
</Show>
);
};
}
// Form Handling with Validation
import { createStore } from "solid-js/store";
interface FormState<T> {
values: T;
errors: Partial<Record<keyof T, string>>;
touched: Partial<Record<keyof T, boolean>>;
isValid: boolean;
isSubmitting: boolean;
}
function createForm<T extends Record<string, any>>(
initialValues: T,
validate: (values: T) => Partial<Record<keyof T, string>>,
onSubmit: (values: T) => Promise<void>,
) {
const [state, setState] = createStore<FormState<T>>({
values: initialValues,
errors: {},
touched: {},
isValid: true,
isSubmitting: false,
});
const validateForm = () => {
const errors = validate(state.values);
setState("errors", errors);
setState("isValid", Object.keys(errors).length === 0);
return Object.keys(errors).length === 0;
};
const setField = <K extends keyof T>(field: K, value: T[K]) => {
setState("values", field as any, value as any);
setState("touched", field as any, true);
validateForm();
};
const handleSubmit = async (e: Event) => {
e.preventDefault();
setState("isSubmitting", true);
if (validateForm()) {
try {
await onSubmit(state.values);
} catch (error) {
console.error("Submit error:", error);
}
}
setState("isSubmitting", false);
};
return {
state,
setField,
handleSubmit,
reset: () => setState({ values: initialValues, errors: {}, touched: {} }),
};
}
// Usage
function RegistrationForm() {
const { state, setField, handleSubmit } = createForm(
{ email: "", password: "", confirmPassword: "" },
(values) => {
const errors: Record<string, string> = {};
if (!values.email.includes("@")) errors.email = "Invalid email";
if (values.password.length < 8) errors.password = "Password too short";
if (values.password !== values.confirmPassword) {
errors.confirmPassword = "Passwords must match";
}
return errors;
},
async (values) => {
await fetch("/api/register", {
method: "POST",
body: JSON.stringify(values),
});
},
);
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="email"
value={state.values.email}
onInput={(e) => setField("email", e.target.value)}
classList={{ error: !!state.errors.email }}
/>
<Show when={state.touched.email && state.errors.email}>
<span class="error">{state.errors.email}</span>
</Show>
</div>
<div>
<input type="password" value={state.values.password} onInput={(e) => setField("password", e.target.value)} />
<Show when={state.touched.password && state.errors.password}>
<span class="error">{state.errors.password}</span>
</Show>
</div>
<div>
<input
type="password"
value={state.values.confirmPassword}
onInput={(e) => setField("confirmPassword", e.target.value)}
/>
<Show when={state.touched.confirmPassword && state.errors.confirmPassword}>
<span class="error">{state.errors.confirmPassword}</span>
</Show>
</div>
<button type="submit" disabled={!state.isValid || state.isSubmitting}>
{state.isSubmitting ? "Submitting..." : "Register"}
</button>
</form>
);
}
```
```tsx
// Testing SolidJS Components
import { render, fireEvent, screen } from "@solidjs/testing-library";
import { describe, it, expect, vi } from "vitest";
// Component to test
function Counter(props: { initialCount?: number }) {
const [count, setCount] = createSignal(props.initialCount ?? 0);
return (
<div>
<span data-testid="count">{count()}</span>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
<button onClick={() => setCount((c) => c - 1)}>Decrement</button>
</div>
);
}
describe("Counter", () => {
it("renders initial count", () => {
render(() => <Counter initialCount={5} />);
expect(screen.getByTestId("count")).toHaveTextContent("5");
});
it("increments count when clicking increment", async () => {
render(() => <Counter />);
const incrementBtn = screen.getByText("Increment");
fireEvent.click(incrementBtn);
expect(screen.getByTestId("count")).toHaveTextContent("1");
});
it("decrements count when clicking decrement", async () => {
render(() => <Counter initialCount={10} />);
const decrementBtn = screen.getByText("Decrement");
fireEvent.click(decrementBtn);
expect(screen.getByTestId("count")).toHaveTextContent("9");
});
});
// Testing with stores
describe("TodoApp", () => {
it("adds a new todo", async () => {
render(() => <TodoApp />);
const input = screen.getByPlaceholderText("What needs to be done?");
const addBtn = screen.getByText("Add");
fireEvent.input(input, { target: { value: "New todo" } });
fireEvent.click(addBtn);
expect(screen.getByText("New todo")).toBeInTheDocument();
});
});
// Testing async resources
describe("UserProfile", () => {
it("shows loading state then user data", async () => {
vi.spyOn(global, "fetch").mockResolvedValue({
ok: true,
json: () => Promise.resolve({ id: 1, name: "John", email: "john@example.com" }),
} as Response);
render(() => <UserProfile userId={1} />);
expect(screen.getByText("Loading user...")).toBeInTheDocument();
await screen.findByText("John");
expect(screen.getByText("john@example.com")).toBeInTheDocument();
});
});
```
## Best Practices
### Reactivity
- Keep signals granular for fine-grained updates
- Use `createMemo` for expensive computations
- Prefer `batch` for multiple updates
- Avoid accessing signals in JSX callbacks unnecessarily
### Performance
- SolidJS doesn't re-render - no need for React.memo
- Use `<Index>` for primitive arrays where index matters
- Use `<For>` for objects/arrays with stable identity
- Lazy load components with `lazy()`
### State Management
- Use signals for local component state
- Use stores for complex nested state
- Use context for dependency injection
- Resources for async data with Suspense
### TypeScript
- Define prop interfaces explicitly
- Use generics for reusable primitives
- Type context values properly
- Leverage type inference where possible
SolidJS powers **Cloudflare Dashboard** and apps requiring maximum performance.
You build blazing-fast reactive applications with fine-grained reactivity and zero virtual DOM overhead.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!