> solid
SolidJS is a reactive UI library that compiles to efficient vanilla JavaScript. It uses fine-grained reactivity with signals and stores, has no virtual DOM, and provides JSX components with excellent performance and small bundle size.
curl "https://skillshub.wtf/TerminalSkills/skills/solid?format=md"SolidJS
SolidJS uses fine-grained reactivity with signals — no virtual DOM diffing. Components run once, and only the specific DOM nodes that depend on changed signals update. This makes it extremely fast.
Installation
# Create SolidJS project
npx degit solidjs/templates/ts my-app
cd my-app
npm install
npm run dev
Project Structure
# SolidJS project layout
src/
├── index.tsx # Entry point
├── App.tsx # Root component
├── routes/ # Page components
│ ├── Home.tsx
│ └── Articles.tsx
├── components/ # Shared components
│ └── ArticleCard.tsx
├── stores/ # Stores for state
│ └── articles.ts
├── lib/ # Utilities
│ └── api.ts
└── index.css
Signals (Primitives)
// src/components/Counter.tsx — basic signals demo
import { createSignal, createEffect, createMemo } from 'solid-js';
export default function Counter() {
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
createEffect(() => {
console.log(`Count is now: ${count()}`);
});
return (
<div>
<p>Count: {count()} (doubled: {doubled()})</p>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</div>
);
}
Components and Props
// src/components/ArticleCard.tsx — component with typed props
import { Component } from 'solid-js';
interface Article {
id: number;
title: string;
slug: string;
excerpt: string;
}
interface Props {
article: Article;
onDelete?: (id: number) => void;
}
const ArticleCard: Component<Props> = (props) => {
return (
<article>
<a href={`/articles/${props.article.slug}`}>
<h2>{props.article.title}</h2>
</a>
<p>{props.article.excerpt}</p>
<button onClick={() => props.onDelete?.(props.article.id)}>Delete</button>
</article>
);
};
export default ArticleCard;
Resources (Data Fetching)
// src/routes/Articles.tsx — async data fetching with createResource
import { createResource, For, Show, Suspense } from 'solid-js';
import ArticleCard from '../components/ArticleCard';
async function fetchArticles(): Promise<Article[]> {
const res = await fetch('/api/articles');
return res.json();
}
export default function Articles() {
const [articles] = createResource(fetchArticles);
return (
<div>
<h1>Articles</h1>
<Suspense fallback={<p>Loading...</p>}>
<Show when={!articles.error} fallback={<p>Error loading articles.</p>}>
<For each={articles()}>
{(article) => <ArticleCard article={article} />}
</For>
</Show>
</Suspense>
</div>
);
}
Stores (Complex State)
// src/stores/articles.ts — store for nested reactive state
import { createStore, produce } from 'solid-js/store';
interface ArticlesState {
items: Article[];
loading: boolean;
filter: string;
}
const [state, setState] = createStore<ArticlesState>({
items: [],
loading: false,
filter: '',
});
export function useArticles() {
async function fetchAll() {
setState('loading', true);
const res = await fetch('/api/articles');
const data = await res.json();
setState({ items: data, loading: false });
}
function removeArticle(id: number) {
setState('items', (items) => items.filter((a) => a.id !== id));
}
function setFilter(query: string) {
setState('filter', query);
}
return { state, fetchAll, removeArticle, setFilter };
}
Control Flow
// src/components/ArticleList.tsx — control flow components
import { For, Show, Switch, Match } from 'solid-js';
export default function ArticleList(props: { articles: Article[]; status: string }) {
return (
<div>
<Switch>
<Match when={props.status === 'loading'}>
<p>Loading...</p>
</Match>
<Match when={props.status === 'error'}>
<p>Error loading articles.</p>
</Match>
<Match when={props.status === 'ready'}>
<Show when={props.articles.length > 0} fallback={<p>No articles.</p>}>
<For each={props.articles}>
{(article) => <ArticleCard article={article} />}
</For>
</Show>
</Match>
</Switch>
</div>
);
}
Routing (SolidStart)
// src/routes/articles/[slug].tsx — SolidStart file-based route
import { useParams } from '@solidjs/router';
import { createResource } from 'solid-js';
export default function ArticlePage() {
const params = useParams();
const [article] = createResource(() => params.slug, async (slug) => {
const res = await fetch(`/api/articles/${slug}`);
if (!res.ok) throw new Error('Not found');
return res.json();
});
return (
<Suspense fallback={<p>Loading...</p>}>
<Show when={article()}>
{(a) => (
<article>
<h1>{a().title}</h1>
<div innerHTML={a().body} />
</article>
)}
</Show>
</Suspense>
);
}
Context (Dependency Injection)
// src/lib/AuthContext.tsx — context for shared auth state
import { createContext, useContext, ParentComponent } from 'solid-js';
import { createStore } from 'solid-js/store';
const AuthContext = createContext<{ user: () => User | null; login: (u: User) => void }>();
export const AuthProvider: ParentComponent = (props) => {
const [state, setState] = createStore<{ user: User | null }>({ user: null });
const value = {
user: () => state.user,
login: (u: User) => setState('user', u),
};
return <AuthContext.Provider value={value}>{props.children}</AuthContext.Provider>;
};
export function useAuth() {
return useContext(AuthContext)!;
}
Key Patterns
- Signals are called as functions:
count()reads,setCount()writes — parentheses matter - Components run once; only signal-dependent expressions re-execute
- Use
<For>for lists (keyed by reference),<Index>for index-based iteration - Use
<Show>for conditional rendering,<Switch>/<Match>for multiple branches - Use
createResourcefor async data — it integrates with<Suspense> - Use stores (
createStore) for nested objects — signals are for primitives - Don't destructure props — it breaks reactivity. Access
props.xdirectly
> related_skills --same-repo
> zustand
You are an expert in Zustand, the small, fast, and scalable state management library for React. You help developers manage global state without boilerplate using Zustand's hook-based stores, selectors for performance, middleware (persist, devtools, immer), computed values, and async actions — replacing Redux complexity with a simple, un-opinionated API in under 1KB.
> zod
You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.
> xero-accounting
Integrate with the Xero accounting API to sync invoices, expenses, bank transactions, and contacts — and generate financial reports like P&L and balance sheet. Use when: connecting apps to Xero, automating bookkeeping workflows, syncing accounting data, or pulling financial reports programmatically.
> windsurf-rules
Configure Windsurf AI coding assistant with .windsurfrules and workspace rules. Use when: customizing Windsurf for a project, setting AI coding standards, creating team-shared Windsurf configurations, or tuning Cascade AI behavior.