Click a React element, change that exact span of .tsx
Every host element you authored reaches the browser carrying its file, line and column, added by the dev server PortBay launches and never written back to your files. Click it, change it, and the edit is spliced into that exact span of the .jsx or .tsx. Pro, early access, opt-in.
How do I visually edit React components in the browser?
Editing React visually comes down to one hard problem: the thing you click is a DOM node, and the thing you want to change is JSX in a file, and nothing in the rendered page says which file. The common way to close that gap is to write an identifying attribute into your components and reprint them through a formatter, which changes your source to make the tool work. PortBay closes it from the other side. Because it launches your Vite dev server, it hands that server a generated config, kept outside your repository, that stamps during serving only — so every host element you wrote arrives in the browser carrying its file, line and column, and none of it is written back to your files. Change the text, the classes or an attribute, and the edit is spliced into that exact span. Pro, early access, opt-in under Settings.
Verified against the product, 2026-09-05.
A React app with every element mapped
PortBay runs your React app at its own .test URL with trusted HTTPS and dispatches board cards into it. The visual editor edits that same running app in your real browser — one live page at one breakpoint, not a multi-frame design canvas.
Your first edit to a .tsx
Four steps, and not one of them installs a package. By the time your dev server boots, the stamping is already there.
- Install PortBay and add your React project
- Turn the visual editor on
- Start the project — nothing is added to it
- Open Edit in browser and change a component
Install PortBay and add your React project
Homebrew installs the app and the bundled portbay CLI. Add the project folder; PortBay reads your package.json, recognises React on Vite, runs your dev server and serves it at its own .test hostname over trusted HTTPS.
brew tap portbay-app/portbay && brew install --cask portbayTurn the visual editor on
It ships to Pro under early access and is off by default. Sign in to Pro, then flip the Early Access toggle. The open-source build does not contain the editor at all — it is not hidden there, it is not compiled in.
Settings › Account › Early AccessStart the project — nothing is added to it
PortBay writes a vite.config.mjs into its own data directory that imports and spreads your config and adds its plugin, then points the dev server at it with one --config flag. Your package.json, lockfile and node_modules are byte-identical before and after. The plugin applies while serving only, so a production build never loads it.
portbay start myappOpen Edit in browser and change a component
The project's Visual editing section has one button. It opens your normal browser at your normal .test URL with the edit bar docked at the top, booting in Preview mode where the page stays fully interactive. Switch to Edit, double-click text to type into the live page, then Save to write to source. Pulling a block of JSX out into its own component file is a single edit here — one of the four stacks where extraction is supported.
https://myapp.testThe tier a JSX file does not get, stated plainly
Host elements you wrote are mapped exactly. Component usages in a .jsx or .tsx file are not, and that is a real boundary rather than a rough edge. It is worth seeing where the line falls before you judge the results.
Host elements are unaffected, because the element allowlist is closed and no type argument is spelled div. A missing coordinate is also not a dead end — further anchor tiers try a unique id in your source and the element's position among same-tag siblings before anything is refused, and an edit that still cannot be pinned reaches you as a picker rather than a guess.
// src/components/PriceCard.jsxexport function PriceCard({ plan }) { return ( <article className="rounded-xl border p-6"> // stamped <h3 className="text-lg font-semibold"> // stamped {plan.name} // refused: an expression </h3> <Badge tone="accent">{plan.tag}</Badge> // NOT stamped </article> );} // why <Badge/> is dropped, and not guessed at:// in a .tsx file <T,>(x: T) => x is a generic parameter list// and it parses as an opening tag. Separating that from <Card/>// needs the TypeScript grammar, not a lexer — and inserting an// attribute into the wrong one is a syntax error in your module.//// what that actually costs: markup rendered by a component you// did not author — a library's own — carries no coordinate.
The pieces underneath
Visual editor
Click an element on your running site in your own browser and edit its text, classes and styles straight into the source file, across 21 stacks.
Explore →Agent dev environment
Stack detection, runtimes, HTTPS, databases and mail, provisioned per project — the running app your agents verify their work against.
Explore →Asked before downloading
Do I have to install a plugin to edit React visually?
Not on Vite. The stamping plugin is compiled into the PortBay binary, written out to PortBay's own data directory, and handed to your dev server through a config generated outside your repo. There is no package to add and none to remove: uninstall PortBay and your package.json, lockfile and node_modules are byte-for-byte what they were. It is not on npm and is not meant to be, because nothing needs to serve it.
I read that PortBay could not stamp JSX. Is that still true?
No. That changed in August 2026 and some documentation has not caught up. Both .jsx and .tsx are scanned script-first now, their host elements carry a data-pb-loc naming file, line and column, and text, class, attribute and page-settings edits all write back — the cross-stack parity test drives all four lanes against a real React fixture at src/App.jsx and every one of them writes. One tier genuinely did not come back, and the next answer is about that.
What does the React lane not map?
Component usages. In a .vue or .svelte file a <Card/> tag carries its own coordinate, so an element that component rendered still traces back to your markup. In a .jsx or .tsx file it does not, because a capitalised tag there can be a TypeScript generic parameter list rather than a component, and inserting an attribute into the wrong one is a syntax error in your module. The practical cost is markup produced by a library you did not author: it carries no coordinate, and PortBay says so rather than guessing.
Does PortBay's visual editor write anything into my React components?
Not to disk. The coordinate is real — it is in the HTML your browser receives — but it is added by the dev-server transform on the way there and never written back to a file, so there is nothing in your repo that exists to make the editor work and nothing to strip out later. This is the structural difference from editors that stamp their own id attribute into your source and reprint the file through a formatter: those change your components before they can edit them, and the reformatting arrives whether you wanted it or not. PortBay splices into the authored bytes and leaves the formatting around them alone.
Does PortBay's visual editor handle className and computed classes like cn() in React?
Yes to className: JSX spells the class attribute className, so PortBay searches JSX-capable files for both className and class rather than only the HTML spelling — searching for class alone silently skipped every JSX class attribute in the tree. Computed classes are handled honestly rather than optimistically: a style change becomes a Tailwind class rewrite only when the utility would actually win the property, and the planner screens a computed className={cn(...)} branch for a conflicting same-family token before it touches the static literal. When it cannot, the change goes to a PortBay override sheet and the notice tells you which happened. Your design tokens are editable too: renaming a token in a v3 tailwind.config or a v4 @theme block rewrites every class reference to it across your authored markup in the same save, or refuses by name if it could not read the whole project.
Can PortBay's visual editor edit React text that comes from props or state?
Only the parts you actually authored. A text run holding an expression is refused by name — the message says it is a template expression and points you at the source — because rewriting bytes that hold {plan.name} would either destroy the binding or write a literal where a value belongs. Static copy in your JSX edits normally. This is the same rule PortBay applies to every template language it supports, not a React-specific limitation.
Does it work with Create React App, webpack, or Next.js?
Not CRA or a bare webpack setup: PortBay can only add instrumentation to dev servers it launches through Vite or Astro, so Babel pipelines get no stamping. Text, class and attribute edits still resolve by searching your source there, but structural edits are refused rather than guessed. Next.js is a separate case with its own supported path and its own page. On the free-versus-paid question: the editor is Pro, in early access behind a Settings opt-in, not compiled into the open-source build, and it refuses to attach while a project is shared over a public tunnel. It is also one live page at one breakpoint at a time — there is no multi-frame infinite canvas.

Give your projects and your agents a real local home.
Download for macOSFree & open source · macOS 11+ on Apple Silicon · Pro from $10/mo