Docs › Web apps
React apps
Vite or Next.js: one command adds the toolbar behind a dev-only guard and sets up your coding agent.
Quick start (dev mode)#
For a React app. Each of the other SDKs has a quick start in its own README.
npm i -D notato @notato/react
npx notato init # adds <Notato /> behind a dev-only guard, sets up your coding agents (MCP server and skills)
npm run dev # start your app
npx notato doctor # checks the whole loop, including that a browser page is connected
init also writes two skills (skip them with --no-skill). notato (/notato in Claude Code, $notato in Codex, or just ask) teaches the agent the whole loop: make the smallest change, commit each fix on its own (staging only its own files, so your uncommitted work is never swept in), resolve with the files and commit, answer a question instead of editing, and undo a change when you ask. One commit per annotation is what makes a revert clean. notato-critique has the agent review the running app itself and file what it finds (see Critique mode). A skill file you have edited is left alone.
init edits Vite and Next.js apps for you (and prints the snippet to paste for anything else). It writes in your project's own style, taking semicolons, quotes, indentation and line width from your prettier config (or the file), so format:check stays green.
A repo with several React apps. Run it from the repo root and it works out which app to use. In a Module Federation setup the host (the shell that loads the modules) gets the toolbar, and every module loaded into it inherits it, so there is nothing to add per module. It recognises the host by how the apps link to each other, including setups where every module also declares the shell as a remote. If it cannot tell, it asks (or, with no terminal, lists the choices and stops):
npx notato init --app app-shell # choose explicitly
npx notato init --app app-shell --agent-dir . # register your agents for the repo root
Apps that are built, not served, in development. import.meta.env.DEV is false under vite build --watch + vite preview, so a dev-only guard would never render there. When a script in package.json does that, init also lets the toolbar render in a build made with VITE_NOTATO=true (put it in .env.local, or set it where the build runs); without the variable a build still ships nothing. Force either way with --guard dev|env.
The exact file and line, even in a built app. React only knows which file an element came from in a development build. In an app that is built and previewed, the agent would get the component name and the selector but no line. The Vite plugin closes that gap: npx notato init --plugin adds notatoSource() (from @notato/vite, which you install in each app) to the plugins array of every Vite app's config. A federation setup needs it in each module, since each is built separately; --plugin-only --app <dir> does one app and nothing else. It does nothing unless VITE_NOTATO=true is set where the build runs, so any other build is exactly what it was. It adds a data-notato-src attribute to each element your JSX writes, Notato reads it, and the agent is told written at src/checkout/PayButton.tsx:18:7. Paths start at the repository root (looking past a submodule's .git file), so in a repo of several apps they begin with the app's folder. Elements a library creates are reported as "inside the element written at …", the nearest one that is tagged. Works with Vite 5 and later.
Run inside a module whose host already has the toolbar, it changes nothing and says why; run inside a module whose host does not, it points you at the host (--force installs in the module anyway). Install notato in the app that gets the toolbar: when your agent starts from a different folder, init registers that app's own node_modules/.bin/notato, since npx only finds it inside the app that installed it.
Undo. npx notato init --revert takes it all back out: the <Notato /> element and its import (the file goes back to what it was, character for character), the lines init added to .gitignore, the skills, the notato entry in each agent's project config file, and the Claude Code registration (claude mcp remove notato). Codex keeps one list of MCP servers for every project, so it is left there unless you name it (--agent codex). It finds the app that has the toolbar the way init finds one, takes --dry-run, --app, --agent, --agent-dir, --no-mcp and --mcp-scope, and is safe to run twice. It leaves your recorded annotations (.notato/), the installed packages and a VITE_NOTATO line in .env.local alone, and says how to remove each. If the code has been changed by hand into something it does not recognise, it leaves that file as it is and tells you what to remove.
By hand:
import { Notato } from "@notato/react"
{import.meta.env.DEV && <Notato mode="dev" project="checkout-web" server="http://localhost:4747" />}
Angular? provideNotato({ project: "checkout-web" }) from @notato/angular in the app's providers: see Notato for Angular.
Something else? The toolbar itself is @notato/browser, with no framework in it; @notato/react and @notato/angular are wrappers around it. Any other app starts it the same way (it takes the same props):
import { createController } from "@notato/browser"
if (import.meta.env.DEV) createController({ project: "checkout-web", server: "http://localhost:4747" })
Press Alt+Shift+A (or the toolbar button), then click an element (disabled buttons and inputs included), select text, drag an area, or Cmd/Ctrl-click several elements. Write a note, say what you want (Fix, Change, Question, Approve or Variants, see below) and how bad it is, and press Cmd/Ctrl+Enter. Hold Shift to use the page instead: a Shift+click goes straight to the app (to open a menu, close a modal, go to the next page) and annotate mode stays on; Esc stops annotating. A numbered pin appears; it turns amber when the agent acknowledges it and green when the agent resolves it.
Ask your agent to "watch Notato and fix what comes in". It calls notato_watch, receives each annotation with its screenshots as images, acknowledges it, fixes the code (the React component and source file are in the annotation), and resolves it with a summary. What you write back, and the version you pick, reach it the same way.
Changing your mind. Hover a green pin and choose Revert this change…, say what was wrong if you like, and press Ask the agent to revert (it says the agent's name when one is connected). The pin turns purple, and the request reaches the agent through notato_watch (or notato_list_open) marked REVERT REQUESTED, with what it recorded when it resolved the annotation (the summary, the files, the commit) and your reason. The agent undoes that change and calls notato_reverted, and the pin turns grey. Cancel request takes it back before the agent has acted. The board has the same two buttons. Notato never edits your code itself: the agent does the undoing, which is why notato_resolve asks it to list the files and commit. A revert can only be requested for a resolved annotation, and needs a server to carry it, so it is not offered in test mode without one.
SDK props#
| Prop | |
|---|---|
mode | dev (default), test, agent |
project | Project id |
server, token | Server URL, and a project token for a shared server |
appName, appVersion | Recorded on every annotation |
plugins | Extra plugins; one with the same id as a built-in replaces it (for example a different screenshot) |
enabled | <Notato /> and provideNotato only: off in production builds by default. Guard with import.meta.env.DEV to ship nothing at all |
screenshots | false never takes a screenshot from this page. A server that has them off wins either way |
hashRoutes, testIdAttributes, maskInputs, shortcut, position, author, persist | See the types |
The toolbar lives in a shadow root, so your styles cannot leak in or out. Identity resolves a test id attribute (data-testid, data-qa, data-cy, data-test), then role and accessible name, then the shortest unique CSS selector; in a React development build it also records the component and source file.