Dify Component Writing Guide
langgenius/dify
Use when implementing or refactoring React/TypeScript components and the task requires decisions about component ownership, feature boundaries, state, data…
Class-based reactive state management for React (Expressive MVC).
$ npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install gabeklein/expressive-mvc expressive-mvc --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/gabeklein/expressive-mvc.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills .claude/skills/expressive-mvc && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "expressive-mvc" agent skill from https://github.com/gabeklein/expressive-mvc/tree/main/skills into .claude/skills/expressive-mvc/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "expressive-mvc", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/gabeklein/expressive-mvc/tree/main/skillsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install gabeklein/expressive-mvc expressive-mvc --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/gabeklein/expressive-mvc.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills .agents/skills/expressive-mvc && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "expressive-mvc" agent skill from https://github.com/gabeklein/expressive-mvc/tree/main/skills into .agents/skills/expressive-mvc/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "expressive-mvc", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install gabeklein/expressive-mvc expressive-mvc --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/gabeklein/expressive-mvc.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills .cursor/skills/expressive-mvc && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "expressive-mvc" agent skill from https://github.com/gabeklein/expressive-mvc/tree/main/skills into .cursor/skills/expressive-mvc/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "expressive-mvc", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/gabeklein/expressive-mvc.git --path skills--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install gabeklein/expressive-mvc expressive-mvc --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/gabeklein/expressive-mvc.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills .gemini/skills/expressive-mvc && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "expressive-mvc" agent skill from https://github.com/gabeklein/expressive-mvc/tree/main/skills into .gemini/skills/expressive-mvc/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "expressive-mvc", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install gabeklein/expressive-mvc expressive-mvcInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/gabeklein/expressive-mvc.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills .github/skills/expressive-mvc && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "expressive-mvc" agent skill from https://github.com/gabeklein/expressive-mvc/tree/main/skills into .github/skills/expressive-mvc/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "expressive-mvc", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install gabeklein/expressive-mvc expressive-mvc --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/gabeklein/expressive-mvc.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills .opencode/skills/expressive-mvc && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "expressive-mvc" agent skill from https://github.com/gabeklein/expressive-mvc/tree/main/skills into .opencode/skills/expressive-mvc/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "expressive-mvc", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
expressive-mvcClass-based reactive state management for React (Expressive MVC).
Expressive Mvc is an agent skill from gabeklein/expressive-mvc. Class-based reactive state management for React (Expressive MVC). Use when writing or refactoring React state - converting useState/useEffect/useMemo hooks, fixing prop drilling, choosing state ownership (State vs Component, has-pool domain rows, region controllers), dependency snapshots, presence boundaries with get(true), providing context, async suspense, router - and when auditing a codebase for fit.
Its SKILL.md is about 5.9k tokens, which your agent loads only when the skill is triggered. The skill folder holds 31 other files (for example `design.md`, `dom/dom.md` and `examples/audit.md`).
It sits in Frontend & Design, covering State management and Refactoring. It works with React. The repository describes itself as: Define classes to organize and control state used by React apps. The licence is MIT.
12 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 80f74b3. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npmcomposerFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
expressive.devFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Expressive Mvc loads about 5.9k tokens when it runs. Until then it costs about 106 tokens; SKILL.md has 2,323 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from gabeklein/expressive-mvc at commit 80f74b3, republished under its MIT licence (© gabeklein). 2,323 words, ~5,862 tokens.
.claude/skills/expressive-mvc/SKILL.md (or your agent's skills folder). This skill also uses 27 other files; get the full folder from GitHub.Class-based reactive state for React. State classes define reactive properties, computed values, async data, and context - all as plain class fields using instruction helpers.
| Package | Status | Description |
|---|---|---|
@expressive/mvc | Published | Framework-agnostic core. Primary import for State, Component, instructions. |
@expressive/react | Published | React adapter. Supplies the host. |
@expressive/dom | Preview | MVC-native client DOM renderer without a framework dependency. |
@expressive/preact | Private | Thin wrapper over React adapter via preact/hooks. Prerelease. |
@expressive/router | Published | Host-agnostic, class-based router built on MVC. |
@expressive/inspect | Published | In-process inspector: registry, ownership tree, path queries, journal. |
npm install @expressive/mvc @expressive/react # + @expressive/router for routing@expressive/mvc is a peer dependency - add it to package.json and import State, Component and every instruction from it, whatever the host. @expressive/react supplies the host: import it once from your entry module so it registers. Its re-exports of the core are deprecated and will be removed, as is the default export of State - adapter-augmented State.* types are invisible through the default alias, so import { State } by name.
React Native / Expo need no setup beyond three boundaries (react/react.md): add @expressive to jest-expo's transformIgnorePatterns; use Router, not BrowserRouter; Link/NavLinks render DOM, so navigate through Router.
For a client-only DOM app without React, install @expressive/mvc and @expressive/dom. @expressive/dom is a sidecar and re-exports nothing - State, Component and instructions come from @expressive/mvc, while render, Portal, style and macro come from @expressive/dom. Its FC model, renderer API, portals, code splitting, and transition constraints are in dom/dom.md.
The most common failure when adopting this library is translating hooks one-for-one before deciding who owns each behavior. Resolve ownership first; APIs come second.
For every stateful concern, pick exactly one owner:
Component - state intrinsic to one display subtree: controls, panels, editors, review/confirm surfaces. Usually defines render(). Fields, handlers, and rendering live on one class. The app/route entrypoint is one even when render() only composes: it owns construction (replica + region State fields), provides implicitly, and ships last-resort catch/fallback - main just mounts it.State - headless model or workflow: network operations, domain rules, cross-view coordination. Views subscribe via State.get() / State.use().Concerns include the rows: UI state describing an entry in a collection - selection, status, progress - is its own State or Component spawned in a has pool; actions about item belong on item. Id-keyed fields (Record<Id, T>, parallel Maps) and (id, value) methods are a missing class; subsets are a second pool admitting the same members.
Counter-rules:
FooState plus FooView just because hooks were present. If the behavior and rendering are one unit, class Foo extends Component is the refactor.Component where a provided State suffices - Components carry React instance surface (props, state, setState, forceUpdate) that makes .get() IntelliSense noisier.Component when state is zero or reducible and no boundary is wanted - a class holding only get(...) fields plus render() is an FC snapshotting .get().Component (children pass through while providing context and boundary placement) is only for cases where React tree placement is the feature: route controllers, progressive boundaries.theme = new Theme() on an existing owner over stacking <Component for> elements for every small controller.Before refactoring existing React code, read react/refactor.md - it expands every step with before/after examples and ends with the review checklist. The short form:
State, Component, or a plain function component for each owner.has pool; actions about item belong on item.composer = new Composer(). Bias one concern per class - barrels are deliberate (page orchestrator, pool owner, mounting shell); shed a second concern the moment it appears, growing a feature or refactoring one alike.<Component for={AppState}> never an instance if only to provide it; the entrypoint Component's own fields provide implicitly - main only mounts <Inbox />..get() instead of receiving drilled props..get() / .use(), destructure for exact nested dependency snapshot.is only to retain the root object alongside sibling destructuring..get(true) where the parent reads the field for its own content.render() and long JSX at conditionals and non-interacting siblings into honestly-named local FCs - units you'd delete or move whole; one-op conditionals and formatted scalars stay inline. Consolidate scopes that share dependencies and hold no nested logic.For large apps, scope a one-shot conversion to route/page controllers and their domain pools; leave mature leaf widgets on hooks until parent domains stabilize.
Write output in the conventions of react/style.md. They are opinion, not semantics - but they exist to keep reactive dependencies auditable, and the golden path applies them by default.
import { State, Component, ref, def, get, has, map, pending, set } from '@expressive/mvc';
import '@expressive/react'; // once, in the entry moduleExtend State to define reactive models. Use State.new() to construct a root instance - it constructs and activates, which plain new does not. Bare new is correct in one place: a class field on another State (theme = new Theme()), where the owner adopts and activates the child, and destroys it in turn.
class Counter extends State {
count = 0;
increment() {
this.count++;
}
}
const counter = Counter.new();
counter.count; // 0
counter.increment();
counter.count; // 1Properties assigned in the class body are reactive - updates notify subscribers. Methods are auto-bound.
Field initializers that configure reactive behavior. Each has multiple overloads - fetch the reference when a task needs them.
| Helper | Use for | Reference |
|---|---|---|
set() | Defaults, placeholders (suspend until assigned), lazy/async factories (suspense), setter callbacks and validation | field/set.md |
get() | Context lookup between States - required or optional upstream, downstream collection | field/get.md |
ref() | Mutable refs (.current), ref callbacks with cleanup, ref proxies | field/ref.md |
map() | Reactive Map field - keyed entries or a keyed spawner, with owned State members and direct render | field/map.md |
has() | Owned collections - an ordered list of values, or a pool of spawned members. Pools are for per-item UI state (selection, progress, row actions). Class first (has(Row)); factory when the seed isn't the init | field/has.md |
def() | Low-level custom property behavior | field/def.md |
For computed values, declare a normal class getter - getters on a State subclass are auto-promoted to memoized, dependency-tracked properties. See state/computed.md for tracking rules and when a derivation should not be a getter.
pending(work) is not a field helper but imports alongside them - it marks the updates work queues non-urgent and resolves once every reader has taken them. See Transitions.
Do not pass a bare promise to set(). Use set(() => promise) or set(async () => value) so work starts during activation/access instead of construction.
class UserProfile extends State {
userId = set<string>();
user = set(async () => {
const res = await fetch(`/api/users/${this.userId}`);
return res.json();
});
email = set('', (value) => {
if (!value.includes('@')) throw false;
});
get displayName() {
return `${this.user.firstName} ${this.user.lastName}`;
}
}// Local state - creates instance, owns lifecycle, subscribes to accessed fields
function MyComponent() {
const { count, increment } = Counter.use();
return <button onClick={increment}>{count}</button>;
}
// Context state - reads nearest provided instance, subscribes reactively
function Child() {
const { count } = Counter.get();
return <span>{count}</span>;
}
// An instance you already hold - place it; it renders and subscribes itself
function Parent({ counter }: { counter: Counter }) {
return <section>{counter}</section>;
}Static .use() and .get() are React hooks - call unconditionally at the top of a component or render(), never inside branch, event handlers, or loops. They build green when misused and crash at runtime. (Instance method get is not a hook.)
Use State.use() when the component should create and own the instance. Use State.get() when the instance comes from context. To render an instance you already hold, make it a Component and place as {instance} - subscription belongs to the placed instance, not the surrounding function. See react/react.md for overloads (optional lookup, required values, computed selector).
Open every subscribing component by destructuring the exact reactive values it renders - nested ones included. Nested observable reads are proxied and tracked automatically, so nested destructuring subscribes to child fields; a child reached through a parent proxy needs no separate subscription.
function OrderSummary() {
const {
status,
customer: {
name,
address: {
city,
} = {},
},
} = Order.get();
return <p>{name} ({city ?? 'no address'}) - {status}</p>;
}This is the norm, not a preference:
order.customer.address.city in every expression.Optional nested objects take in-place defaults (= {}) rather than a separate unwrap step. The same rule applies to this inside Component.render() and subcomponents - injected parents (inbox = get(Inbox)) are read thru that snapshot, never a second Inbox.get().
Proxies pass assignments through to the instance, nested objects included - no unwrapping:
const form = LoginForm.get(); // whole object needed - take it directly
<input value={form.username} onChange={(e) => (form.username = e.target.value)} />
const { transfer, confirmed } = ReviewStep.get();
<button onClick={() => (transfer.step = 'generate')} disabled={!confirmed} />
const { is: review, confirmed, hasBlocking } = ReviewStep.get(); // `is`: root beside siblingsis is only for keeping the root alongside sibling destructuring. Unwrapping every writable object through it is the most common misuse.
get(true)Default: a self-contained widget mounts unconditionally, reads its own context, and falls thru when its reason to render is unset - a parent read existing only to gate belongs in the widget. The secondary shape, when the parent reads the field for its own content: parent gates, child asserts with get(true):
function SettingsContent() {
const { draft } = SettingsState.get();
return (
<div className="settings-layout">
<LocationList />
{draft && <SettingsEditor />}
</div>
);
}
function SettingsEditor() {
const {
saveSettings,
saving,
draft: {
bankAccount,
categoryAccounts,
},
} = SettingsState.get(true); // Required<T> - throws if an accessed value is undefined
return <section className="settings-editor">...</section>;
}This gives the child a strong contract - no fallback values threaded through its body. Declare gateable fields optional (draft?: SettingsLocation), not | null: the runtime check rejects only undefined, and Required<T> does not strip null from a union (see react/react.md). Both shapes in full: react/refactor.md step 12.
Pass the class: <Component for={TransferState}>. Pass an instance only when something else owns it (<Component for={counter}> after Counter.use()). One item per element - several belong to a parent State owning them as fields, which makes them siblings; nesting <Component for> is a smell (the outer cannot get the inner). is, attributes, boundaries: state/context.md.
A Component is a State that renders itself. It provides context automatically and supports suspense/error boundaries.
class CounterView extends Component {
count = 0;
increment() {
this.count++;
}
render() {
const { count, increment } = this;
return <button onClick={increment}>{count}</button>;
}
}
<CounterView />;
const counter = CounterView.new();
<>{counter}</>;An activated instance renders directly, arrays included; its owner keeps lifecycle, so unmount detaches without destroying.
PascalCase methods become reactive subcomponents - extension points a subclass would replace or wrap, not general decomposition. Otherwise write a freestanding FC calling MyComponent.get(). Details: react/component.md.
Every broad rule here has a locality constraint. Apply both halves. When auditing a result, weigh findings by the severity labels defined in react/refactor.md - invariant, default, heuristic, style - and never fail a heuristic on its numerical signal alone.
| Rule | Counter-rule |
|---|---|
| Reactive fields are assigned directly | Keep a method when the write validates, normalizes, coordinates fields, or triggers behavior. Delete methods whose body is only this.x = value. |
| Derived values become getters | Only when shared by multiple consumers, semantic to the domain, expensive, or a deliberate part of the state's API/introspection surface. Single-consumer display derivations live in the consuming component - but judge meaning, not reference counts. |
Contextual components read via .get() | Pure presentation components may still take plain props. Context replaces drilled state, not every value. |
| PascalCase subcomponents compose renders | Only for genuine extension points a subclass would replace or wrap. Implementation scopes are freestanding FCs using .get(). |
| Extract long conditional JSX (~10+ lines or ~5+ levels) | Keep branches together when they share dependencies, read locally, and contain no nested logic. |
is retains the raw instance | Only alongside sibling destructuring from the same snapshot. Writes through proxies are transparent; nested objects need no unwrapping. |
| State about a collection entry lives on the entry's class | Payload keys that never drive UI stay as one info subobject on the member - a reactive field without a reader is pure cost. |
Fetch these for detailed documentation when the task requires deeper knowledge. Read react/refactor.md in full before any hook-migration or refactor task.
get/set surface, render composition as a designated seam, typed lifecycle hooks, transparent writes. Read before characterizing a design choice as accidental. For positioning against alternatives (Zustand, Jotai, MobX, Redux Toolkit, plain Context), see expressive.dev/llm/comparisons.md.is, subcomponent overuse, extract-then-consolidate, review checklist.get() method: read values, run effects, context lookup.set() method: write values, listen to updates, events, destroyhot seamInstructions: field/*.md, linked from the helper table.
get(true), computed selector), providing state, transparent writes, ForceRefresh, Vite hot reloadRouter vs BrowserRouter, page-data ownership, navigation settlement, testing, host boundaries, and unsupported browser behavior@expressive/inspect: install entry, instances in process vs addresses across a boundary, ownership, stored-value reads with absent, labels under minification, frame journal with cause, act, orphans and health(), bridge and Vite relayRunnable examples - optional; this skill is complete offline. https://expressive.dev/examples/<group>/<name> serves a working program's every source file as plain HTML (no JS needed). Fetch one for a full reference implementation rather than a snippet:
| Group | Pages |
|---|---|
featured | forms, kanban, spreadsheet, stopwatch, tictactoe |
essentials | counter, computed, fetch, async |
component | props, subcomponents, lifecycle, injection, headless, suspense, boundary, custom |
composition | nested, context, concerns, extension, globals |
instructions | set, set-factory, set-computed, get, get-downstream, ref, ref-multiple, map, map-insert, has, has-list, def |
router | overview, browser, params, query, guards, transitions, nav, wizard |
Audit a conversion as a separate pass over the diff - self-audits while authoring under-report architecture gaps.
Fit and migration candidates: examples/audit.md; approved migrations follow react/refactor.md. Positioning: website-only why, comparisons, and dom vs React.
© gabeklein, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 27 other files in skills of gabeklein/expressive-mvc.
Open the folder on GitHubat commit 80f74b3
Expressive Mvc next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Expressive Mvc this skillgabeklein/expressive-mvc | 103 | — | ~5.9k | Automated safety check: Pass | MIT | |
| Dify Component Writing Guidelanggenius/dify | 158k | — | ~626 | Automated safety check: Pass | Custom licence | |
| React Idiomsirahardianto/awesome-agv | 157 | — | ~3.8k | Automated safety check: Pass | MIT | |
| Frontend Developmentavibebuilder/claude-prime | 120 | — | ~1.3k | Automated safety check: Pass | MIT | |
| Vercel React Best Practicessanity-io/sanity | 6.4k | 129 repos | ~1.6k | Automated safety check: Pass | MIT | |
| Frontend Module Standardssiteboon/claudecodeui | 14k | — | ~2.6k | Automated safety check: Pass | AGPL-3.0 |
langgenius/dify
Use when implementing or refactoring React/TypeScript components and the task requires decisions about component ownership, feature boundaries, state, data…
irahardianto/awesome-agv
React 19+ patterns: custom hooks, Suspense boundaries, state management, component composition, and web performance.
avibebuilder/claude-prime
A skill your agent uses for ANY work involving React, Next.js, TypeScript, or Tailwind in the browser layer.
sanity-io/sanity
React and Next.js performance optimization guidelines from Vercel Engineering.
siteboon/claudecodeui
Enforces one repository's React and TypeScript module layout for code under src/: source-root imports, feature barrels, deliberate exports and no deep imports.
trycompai/crm
nuqs (type-safe URL query state) best practices for Next.js and other React frameworks.
Works with
Categories
Class-based reactive state management for React (Expressive MVC). Expressive Mvc is an agent skill from gabeklein/expressive-mvc. Class-based reactive state management for React (Expressive MVC).
Expressive Mvc fits situations like: refactoring React state - converting useState/useEffect/useMemo hooks; fixing prop drilling; choosing state ownership (State vs Component; has-pool domain rows.
Run `npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a claude-code`. Or copy the skill folder (skills in gabeklein/expressive-mvc) into .claude/skills/expressive-mvc in your project. Claude Code loads it when a task matches its description.
Run `npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a codex`. Or copy the skill folder (skills in gabeklein/expressive-mvc) into .agents/skills/expressive-mvc in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add gabeklein/expressive-mvc --skill expressive-mvc -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/expressive-mvc, .gemini/skills/expressive-mvc, .github/skills/expressive-mvc and .opencode/skills/expressive-mvc in your project.
Going by SKILL.md and its folder, Expressive Mvc needs the command-line tools its instructions call (npm and composer). Our summary lists: Node.js.
SKILL.md names 1 domain. In commands or code: expressive.dev; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Expressive Mvc is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.9k tokens (SKILL.md is roughly 23k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Expressive Mvc: Dify Component Writing Guide (langgenius/dify, 158k stars), React Idioms (irahardianto/awesome-agv, 157 stars), Frontend Development (avibebuilder/claude-prime, 120 stars) and Vercel React Best Practices (sanity-io/sanity, 6.4k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
gabeklein (a GitHub user) maintains it in gabeklein/expressive-mvc, which has 103 GitHub stars. The repository was last updated on October 9, 2026.
Source: gabeklein/expressive-mvc on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.