Skip to content
PortBay

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.

The short answer

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.

In the app

A React app with every element mapped

acme-shop · Board6 cards3 agents online
Pending2
infra
Rate-limit the API
P1
Dark mode polish
P2
Todo2
auth
Add OAuth login
P0Claude Code0/3oauth-login
Auto-dispatching
implement OAuth on app.test
db
Migrate to Postgres
P1
In Progress1
Fix flaky checkout test
P0
Done2
Set up Mailpit inbox
P2
Seed demo data
P2
1/5 · AssignA card lands in Todo and @claude is mentioned

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.

Walkthrough

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.

  1. Install PortBay and add your React project
  2. Turn the visual editor on
  3. Start the project — nothing is added to it
  4. 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 portbay

Turn 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 Access

Start 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 myapp

Open 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.test
Without PortBay

The 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.

Questions

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.

PortBay mascot — a friendly blue tugboat

Give your projects and your agents a real local home.

Download for macOS

Free & open source · macOS 11+ on Apple Silicon · Pro from $10/mo