vscode-react-webview-architecture
Skill646 starsChanged 24 days agoMITView on GitHub
Architecture patterns for React-based webviews in the Rayfin VS Code extension (packages/tools/vscode). Use when creating new webview components, working with state management (Context API), integrating Fluent UI components, solving stale closure bugs with refs, or debugging webview rendering issues. Does NOT cover tRPC messaging (see vscode-webview-trpc-messaging skill).
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 26
Every webview boots through packages/tools/vscode/src/webviews/index.tsx:
- Distinctive: line 95
Always use functional updates: when state depends on previous value:
- Very specific: line 125
Use @fluentui/react-components (v9), themed via DynamicThemeProvider:
- Distinctive: line 143
Avoid negative margins — fix layout with proper flexbox
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
React Webview Architecture
Patterns and conventions for React webviews in Project Rayfin.
Related skills (do not duplicate):
- vscode-webview-trpc-messaging — tRPC routers, procedures, telemetry, AbortSignal, subscriptions, WebviewController
Full reference: See references/REACT_ARCHITECTURE_GUIDELINES.md
When to Use
- Creating or modifying a webview
- Adding new components inside
packages/tools/vscode/src/webviews/ - Working with React Context state management
- Integrating Monaco Editor
- Debugging stale closure issues in event handlers
Rendering Pipeline
Every webview boots through packages/tools/vscode/src/webviews/index.tsx:
root.render(
<DynamicThemeProvider useAdaptive={true}>
<WithWebviewContext vscodeApi={vscodeApi}>
<Component />
</WithWebviewContext>
</DynamicThemeProvider>,
);DynamicThemeProvider— adapts Fluent UI theming to VS Code's active color themeWithWebviewContext— providesvscodeApi(postMessage) via React ContextWebviewRegistry— maps webview names → React components (inpackages/tools/vscode/src/webviews/api/configuration/WebviewRegistry.ts)
Configuration from the extension host is read via useConfiguration<T>().
File Organization
viewName/
├── ViewName.tsx # Main component
├── viewName.scss # Styles
├── viewNameContext.ts # Context + state types (if complex)
├── viewNameController.ts # WebviewController subclass (extension-side)
├── viewNameRouter.ts # tRPC router (extension-side, see vscode-webview-trpc-messaging skill)
├── constants.ts
├── components/ # Sub-components
├── hooks/ # Custom React hooks
├── types/ # TypeScript types
└── utils/ # HelpersComponent Hierarchy
Example component hierarchy for a typical view:
MyView
├── Header
├── ToolbarActions
├── TabList
│ ├── ContentTab
│ │ └── ContentPanel
│ └── EditorTab
│ └── MonacoEditor
└── StatusBarState Management
Simple views: local useState + props
Complex views: React Context with [state, setState] tuple
export const MyViewContext = createContext<
[MyViewContextType, React.Dispatch<React.SetStateAction<MyViewContextType>>]
>([DefaultMyViewContext, () => {}]);
// Provider in parent
const [currentContext, setCurrentContext] = useState(DefaultMyViewContext);
<MyViewContext.Provider value={[currentContext, setCurrentContext]}>
// Consumer in child
const [currentContext, setCurrentContext] = useContext(MyViewContext);Always use functional updates when state depends on previous value:
setCurrentContext((prev) => ({
...prev,
isLoading: true,
activeQuery: { ...prev.activeQuery, pageNumber: 1 },
}));Stale Closure Pattern (CRITICAL)
Third-party components that bind event handlers at initialization don't update when state changes. Always use refs to access current data in those handlers:
const dataRef = useRef(data);
useEffect(() => {
dataRef.current = data;
}, [data]);
const onClick = useCallback((event) => {
const item = dataRef.current[event.detail.args.row]; // ✅ always current
// NOT: data[event.detail.args.row]; ❌ stale closure
}, []); // stable deps onlyWhy: Some third-party components bind handlers once at init time. Without refs, handlers see the data from initialization, not the latest state.
Fluent UI Integration
Use @fluentui/react-components (v9), themed via DynamicThemeProvider:
| Component | Usage |
ProgressBar | Loading states |
Button, ToggleButton | Toolbar actions |
Tab, TabList | View switching |
Dropdown, Option | Selection (ViewSwitcher) |
Badge | Status/preview indicators |
MessageBar | Info/warning messages |
Skeleton, SkeletonItem | Loading placeholders |
Styling
- Each component gets its own
.scssfile, imported directly - Shared styles in
sharedStyles.scss, applied via@extend - Consistent spacing unit:
10pxwith flexboxrow-gap/column-gap - No inline styles — move to SCSS files
- Avoid negative margins — fix layout with proper flexbox
.myView {
display: flex;
flex-direction: column;
height: 100vh;
row-gap: 10px;
}Custom Hooks
| Hook | Location | Purpose |
useSelectiveContextMenuPrevention() | packages/tools/vscode/src/webviews/api/webview-client/utils/ | Prevents browser context menu everywhere except Monaco editors. Call once in top-level view. |
Conditional Rendering Patterns
Object-based switch:
In this repository76 other files it gives its agents.
- common/config/typedoc/AGENTS.mdAGENTS.md
- docs/site/AGENTS.mdAGENTS.md
- .github/skills/react-best-practices/AGENTS.mdAGENTS.md
- packages/docgen/AGENTS.mdAGENTS.md
- packages/guide/AGENTS.mdAGENTS.md
- packages/host-docs/AGENTS.mdAGENTS.md
- packages/tools/AGENTS.mdAGENTS.md
- packages/tools/cli/AGENTS.mdAGENTS.md
- packages/tools/cli/assets/agent-files/AGENTS.mdAGENTS.md
- packages/tools/common/AGENTS.mdAGENTS.md
- packages/tools/docs-lib/AGENTS.mdAGENTS.md
- packages/tools/mcp/AGENTS.mdAGENTS.md
- packages/tools/vscode/AGENTS.mdAGENTS.md
- packages/typescript-sdk/AGENTS.mdAGENTS.md
- packages/typescript-sdk/auth/AGENTS.mdAGENTS.md
- packages/typescript-sdk/core/AGENTS.mdAGENTS.md
- packages/typescript-sdk/data/AGENTS.mdAGENTS.md
- packages/typescript-sdk/storage/AGENTS.mdAGENTS.md
- packages/udf/udf-worker-extension/AGENTS.mdAGENTS.md
- samples/data-app/AGENTS.mdAGENTS.md
- samples/getting-started-auth/AGENTS.mdAGENTS.md
- samples/notes-app/AGENTS.mdAGENTS.md
- samples/todo-app/AGENTS.mdAGENTS.md
- samples/universal-app/template/AGENTS.mdAGENTS.md
- samples/welcome-app-react-auth/AGENTS.mdAGENTS.md
- samples/welcome-app-react-no-auth/AGENTS.mdAGENTS.md
- samples/welcome-app-typescript/AGENTS.mdAGENTS.md
- hotfix-releaseSkill
- launch-vscode-extensionSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-bulk-archive-changeSkill
- openspec-continue-changeSkill
- openspec-exploreSkill
- openspec-ff-changeSkill
- openspec-new-changeSkill
- openspec-onboardSkill
- openspec-sync-specsSkill
- openspec-verify-changeSkill
- privacy-dfdSkill
- rayfin-cli-uxSkill
- vercel-react-best-practicesSkill
- react-clean-architectureSkill
- run-cli-e2e-locallySkill
- vscode-webview-trpc-messagingSkill
- rayfin-connectorsSkill
- rayfin-functionsSkill
- rayfinSkill
- rayfin-storageSkill
- rayfin-getting-startedSkill
- app-designSkill
- app-validationSkill
- dax-authoringSkill
- fabric-cliSkill
- fabric-sdkSkill
- playwright-cliSkill
- query-designSkill
- schema-discoverySkill
- visualsSkill
- analyticsSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

