ellgot
v0.5.3
Published
A devtools-style React overlay for live-editing a running app's styles, with write-back to source.
Readme
ellgot
A devtools-style React overlay for live-editing a running app's styles — external CSS, CSS Modules, inline style — with write-back to the real source file(s) on disk. Works with Vite or Next.js (App Router, webpack dev — see Requirements for what's supported where). See PLAN.md for the full design doc, decisions made, and known risks.
Toggle on an element picker, select anything in the DOM, see its styles broken out by source (external stylesheet / CSS Module / inline / computed), edit a property with instant live preview, and write the whole batch back to source with an inline diff to approve first.
What's in the package
The browser can select elements and preview changes, but only a Node-side process can write files — so alongside the overlay itself, there's one write-back entry point per bundler:
ellgot— the browser overlay (React). Same for every bundler.ellgot/vite-plugin— a Vite plugin registering dev-server middleware that receives batched edits, computes a diff, and (after you approve it in the panel) writes them to source.ellgot/next— a Route Handler factory for Next.js App Router, doing the same job. Next has no plugin hook for a package to auto-register a dev route the way Vite does, so this one's a small file you add yourself (see Install below) rather than a config entry.
Setting this up with an AI coding agent's help? Point it at AGENTS.md (also shipped at the published package's own root) — it covers what to check in the target repo first and the required integration steps for whichever bundler you're on, since the wrap-your-app-root API below is easy for an agent to get wrong if it's seen an older version of this package.
Requirements
- React 19.
ellgotlocates the JSX source of a clicked element by readingfiber._debugStack— an undocumented React internal that replaced React 19's own predecessor (_debugSource, dropped between major versions). Older React versions are not just untested, they're known-incompatible: this field doesn't exist there. Seesrc/core/sourceLocator.ts. - One of:
- Vite 7, dev mode only.
ellgot's style resolver reads injected CSS Modules via<style data-vite-dev-id="...">, the format Vite 7's dev server actually uses (not<link href>). Earlier Vite majors may format this differently — untested, not verified either way. Supports both CSS and inline JSXstylewrite-back. - Next.js, App Router,
next devon webpack (i.e. not--turbopack/--turbo— Turbopack's own dev-mode CSS sourcemap support has open upstream bugs that make reliable source attribution impractical right now; revisit once that stabilizes). RequirestranspilePackages: ["ellgot"]innext.config.ts(ellgot'sexportsship raw.ts/.tsx, which Next's webpack doesn't transpile insidenode_modulesby default) and one small Route Handler file you add yourself — see Install below. CSS Module / external stylesheet write-back only — inline JSXstylewrite-back isn't supported yet (surfaced as an explicit outcome in the panel, not silently dropped):fiber._debugStackreports a position in the compiled/served code, which needs a sourcemap to translate back to real source, and Next's dev server doesn't serve one at a predictable URL the way Vite'stransformRequestAPI lets the Vite plugin ask for one directly. Pages Router isn't supported.
- Vite 7, dev mode only.
- Dev-only by construction throughout:
<EllgotRoot>/<StyleInspector>self-guard viaprocess.env.NODE_ENV === "production"(rendering justchildren, no added DOM, in a production build — this check, not Vite'simport.meta.env.DEV, is what makes both bundlers work from the same source),ellgotVitePluginregisters withapply: "serve", and the Next.js Route Handler checksprocess.env.NODE_ENVitself as defense-in-depth (Next has no build-time "dev only" registration for a Route Handler the way a Vite plugin has).
Install
npm install -D ellgotVite
// vite.config.ts
import path from "node:path";
import { ellgotVitePlugin } from "ellgot/vite-plugin";
export default defineConfig({
plugins: [
react(),
ellgotVitePlugin({
// Every write is confined inside this directory — required, no default.
projectRoot: path.resolve(__dirname),
// Optional: flag files that might be shared with other apps in your
// repo (surfaced as a warning before Confirm, not a block). Defaults
// to "anything under a packages/ directory" — override if your repo
// uses a different convention (libs/, modules/, ...), or pass
// () => false to disable the warning entirely.
// isSharedPath: (resolvedFile) => resolvedFile.includes("/libs/"),
}),
],
});// wrapping your app's root, e.g. main.tsx
import { EllgotRoot } from "ellgot";
<EllgotRoot>
<App />
</EllgotRoot>;Next.js (App Router, webpack dev)
// next.config.ts — required: ellgot's exports ship raw .ts/.tsx, which
// Next's webpack doesn't transpile inside node_modules by default.
const nextConfig: NextConfig = {
transpilePackages: ["ellgot"],
};// app/ellgot/edit/route.ts — Next has no configureServer-equivalent plugin
// hook for a package to auto-register a dev route the way Vite's does, so
// this one file is added by hand. Not app/__ellgot/edit/route.ts — Next
// treats a leading `_` as a private, unrouted folder, so that path would
// 404 unconditionally.
import { createEllgotRouteHandler } from "ellgot/next";
export const { POST } = createEllgotRouteHandler({ projectRoot: process.cwd() });// wrapping your app's root — app/layout.tsx
import { EllgotRoot } from "ellgot";
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html lang="en">
<body>
<EllgotRoot>{children}</EllgotRoot>
</body>
</html>
);
}<EllgotRoot> renders <StyleInspector/> internally — for standard usage you don't import or mount StyleInspector yourself. It wraps (not sits beside) your app root because Device Simulation works by relocating your app's own React tree into a real <iframe> (so it gets a genuinely independent, accurate window.innerWidth/media-query environment) without ever unmounting it — state, routing, and in-progress edits all survive toggling the device preview on and off. StyleInspector is still exported for advanced/manual composition, but it throws if rendered without an <EllgotRoot> ancestor. Both EllgotRoot and StyleInspector already carry their own "use client" directive for Next.js (a no-op under Vite) and already guard against Next's SSR pass touching browser-only APIs — you shouldn't need next/dynamic({ ssr: false }) or any client-only wrapper of your own around it.
Local development (this repo)
package.json exports still point at raw src/ so a file: dependency is transformed by the consuming app's own bundler — editing ellgot's source shows up live via HMR, no rebuild step. vite-plugin/ and next-plugin/ are the exception: both are Node-side code that runs outside the browser bundle entirely (inside Vite's own config process, or inside Next's Route Handler runtime), so changes there need a dev-server restart to take effect, not a save.
npm install
npm test
npm run demoReleasing a new version to npm is documented in PUBLISHING.md.
Status
Extracted from a private app repo where it originated. Functional and in active use in that (Vite) app. Next.js support (App Router, webpack dev) has been verified end-to-end against a real create-next-app project — CSS Module resolution and write-back, Device Simulation, and SSR/hydration all confirmed working — but hasn't seen production use the way the Vite path has.
