Back to skills
SKILL.md
Solidjs Expert
ASecurityExpert in SolidJS reactive framework with fine-grained reactivity and performance optimization. Use when building applications with the solidjs framework.
- 8 stars
- 0 votes
- 0 copies
- 4 views
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill solidjs-expert --agent claude-codeAre you the author of Solidjs Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-solidjs-expert)---
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.
Attribution
Comments
Loading comments…