Getting started
Install with React Router
Passback adds a small button to your site while you develop. Click it, click anything on the page, and write what should change. The comment goes to the coding agent you already have open.
Before you start
- A React Router 7 project that runs on Vite.
- Node 22 or later.
- One of Claude Code, Codex or Cursor, opened in the project.
Install
In your project folder, add the two packages:
npm i -D passback @passback/vite-pluginSet the project up:
npx passback init --apply- Init does: detects that this is a React Router project, safely edits a recognised Vite config to add the plugin, and prints the React Router head instructions below. It installs no packages and never edits your layout.
- You do: open
app/root.tsx, apply the printed instructions to the<head>ofLayoutwhile keeping everything it already has, and restart your dev server.
.: manually edit app/root.tsx Layout's <head>; pages show the widget only after this helper call. .: import { passbackHead } from '@passback/vite-plugin/head'; add {passbackHead().map(({ tag: Tag, attrs, children }, i) => <Tag key={i} {...attrs} {...(children === undefined ? {} : { dangerouslySetInnerHTML: { __html: children } })} />)} inside Layout's <head>.Check
vite.config.ts. Thepassback()plugin sits besidereactRouter(). If init could not edit the config safely, it leaves the file as it is and prints the line to add by hand:// vite.config.ts import { reactRouter } from '@react-router/dev/vite'; import passback from '@passback/vite-plugin'; export default { plugins: [reactRouter(), passback()], };Add the helper to
app/root.tsx. Add the import at the top of the file:import { passbackHead } from '@passback/vite-plugin/head';Then render this expression inside the
<head>of your existingLayout. KeepMeta,Linksand the rest of the document as they are:{passbackHead().map(({ tag, attrs, children }, index) => tag === 'meta' ? ( <meta key={index} {...attrs} /> ) : ( <script key={index} {...attrs} dangerouslySetInnerHTML={{ __html: children ?? '' }} /> ), )}Start your dev server as usual, or restart it if it was running. The Passback button appears in the bottom right corner.
Restart your agent in the project and approve the
passbackserver when it asks. Then start a session: type/passbackin Claude Code or Cursor,$passbackin Codex. The agent waits for your first comment.
What a comment carries
- The element you marked, your note and the page it was on.
- No React component names, source files or line numbers.
Compare frameworks in What a comment carries.
Comments from a build
In development a comment goes to the coding agent. In a staging build configured with mode: 'relay', a siteKey, an endpoint and stagingModes, comments go to the hosted relay:
passback({
mode: 'relay',
siteKey: '…',
endpoint: '…',
stagingModes: ['staging'],
});Production builds outside stagingModes ship nothing because the helper returns nothing. The tags the helper writes follow the plugin's widget and screenshot settings. For the options, see Install with Vite.
Without the plugin, see Where the tag goes in your framework.