Getting started
Two minutes from a fresh project to the first applied annotation. Everything here is development-only; nothing changes in your production build.
Install
pnpm add -D redlining
npx redlining init
redlining init is idempotent. It writes three things and skips whatever
already exists:
-
app/api/redlining/route.ts(orsrc/app/…) — the endpoint that saves a session into.redlining/. - a
.redlining/line in.gitignore. -
.claude/commands/redline.md— the/redlineslash command for Claude Code; other agents read.redlining/annotations.mddirectly.
If there is no app/ directory it prints what to create instead of guessing.
Set up with an agent
Paste this into your coding agent (Claude Code or any other) inside the repository. It installs, wires, initialises and verifies Redlining, and reports back without committing.
Set up Redlining in this repository. Redlining is a development-only overlay for Next.js (and Vite + React) that lets me annotate the running app; the annotations become a file-anchored spec you apply with the /redline command. Do exactly the steps below, verify, and report. Do not commit.
1. Detect the setup. Look for next.config.* or vite.config.*, and for the lockfile to learn the package manager (pnpm-lock.yaml, yarn.lock, package-lock.json, bun.lockb). Use that package manager for everything; never switch it.
2. Install: add `redlining` as a dev dependency (e.g. `pnpm add -D redlining`). If the registry does not have the package, stop and tell me instead of improvising.
3. Wire it in.
Next.js (16 or newer, React 19):
- In next.config.*, wrap the exported config with `withRedlining` from 'redlining/next'. Preserve everything already there; if the config is a function, pass the function to withRedlining unchanged.
- In the ROOT layout (app/layout.tsx or src/app/layout.tsx, not a nested layout), import { Redlining } from 'redlining' and render <Redlining /> as the last child of <body>.
Vite + React:
- In vite.config.*, add `redlining()` from 'redlining/vite' as the first plugin.
- Render <Redlining /> once in the root component.
4. Run `npx redlining init`. It writes app/api/redlining/route.ts (or src/app/...), a `.redlining/` line in .gitignore, and .claude/commands/redline.md, and skips anything that already exists. If it reports that there is no app/ directory (Vite, Pages Router), do not invent a route; the overlay's "Copy prompt" works without one.
5. If the components that should be annotatable live outside app/, components/ and src/, pass the directories as `{ include: [...] }` in the second argument of withRedlining (or redlining()).
6. Verify, then stop the server:
- Start the dev server, fetch one page, and confirm the HTML contains `data-rl="…"` attributes on host elements such as <nav> or <button>.
- Run the production build and confirm its output contains no `data-rl` attribute.
- Next.js only: POST to /api/redlining with body {"session":{"route":"/","url":"http://localhost:3000/","viewport":{"w":1,"h":1},"annotations":[]}} while the dev server runs; expect 200 and .redlining/annotations.md to appear. Delete .redlining/ afterwards.
7. Report: the files you changed, the include list in effect, how to open the overlay (Alt+R in the running app), and anything you could not verify and why.
Afterwards, press Alt+R in the running app, annotate, save, and run
/redline.
Next.js setup
Requires Next.js 16 or newer and React 19. Both the default Turbopack dev server and
next dev --webpack are supported.
// next.config.ts
import { withRedlining } from 'redlining/next'
export default withRedlining({
// your config
})
// app/layout.tsx
import { Redlining } from 'redlining'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Redlining />
</body>
</html>
)
}
withRedlining() returns a config function so it can see the phase: the loader
is registered only while the dev server runs. Pass a function config of your own and it is
composed, sync or async. By default files under app/,
components/ and src/ get anchors; change that with the
include option (see Options).
app/** inside apps/web/ still works.
Vite setup
// vite.config.ts
import react from '@vitejs/plugin-react'
import { redlining } from 'redlining/vite'
import { defineConfig } from 'vite'
export default defineConfig({ plugins: [redlining(), react()] })
Mount <Redlining /> in your root component. The plugin runs only under
vite dev and stamps files under src/ by default. Pass
redlining({ endpoint: true }) to serve the save endpoint from the dev server
at /api/redlining (it writes to .redlining/ and reads
reply.md like the Next.js route); without it, Save downloads the files.
Other stacks: Angular, Vue, plain HTML
Everything that reads the DOM works on any page: select, draw, move, tweak with provenance
and class hints, notes with reference images, the Markdown export. What needs a framework
is the exact file:line (JSX is stamped by the loader; HTML templates are not
yet, so those anchors are selector-only) and the component names (React's owner chain;
Angular's ng.getComponent in dev mode; Vue 3's instance names).
<!-- dev only: React is bundled, ~155 KB gzipped -->
<script type="module" src="/node_modules/redlining/dist/standalone.js"
data-redlining data-endpoint="off"></script>
// or from code
import { mount } from 'redlining/standalone'
const unmount = mount({ endpoint: false, hotkey: 'Alt+R' })
data-endpoint="off" (or endpoint: false) makes Save download
annotations.md, annotations.json and the images; the browser may
ask once before allowing several downloads. Move them into .redlining/ and
run /redline. Point data-endpoint at a path instead when your
dev server hosts the handler (createHandlers from
redlining/next/route takes a Fetch Request; the Vite plugin
shows how to adapt Node's).
Your first session
- Start the dev server and open a page. A small pen button sits bottom-right (Next DevTools keeps bottom-left).
- Press Alt+R, or click the pen. The toolbar appears and the cursor becomes a crosshair.
-
Hover anything. The smallest host element under the cursor is outlined,
with a badge such as
MainNav · app/(app)/layout.tsx:42 · <nav>. Press [ to select the parent instead, ] to come back down; ⌥+scroll does the same. - Click. A numbered pin drops and a note popover opens with the textarea focused. Choose Change or Remove, type the note, press ⏎ to save (⇧+⏎ for a newline, Esc to cancel).
- Add more. Draw mode (D) for something that does not exist yet, Move mode (M) for something that should live elsewhere. The guide covers each.
- Review with L: the list panel shows every annotation; click a note to edit it, the × to delete.
- Save with ⌘+⏎ (or the green send button). A toast confirms: Saved — run /redline, or hand .redlining/ to your agent.
Applying with your agent
In Claude Code, in the same project:
/redline
The command tells the agent to read .redlining/annotations.md, look at the
screenshot if there is one, apply every annotation in order at the exact anchor, reuse
existing components and design tokens, change nothing that is not listed, verify in the
running app, report done / partial / skipped per annotation, write that report to
.redlining/reply.md, and finally delete the export files. The file may hold
several routes when you annotated more than one page.
Your session in the browser is not deleted by that. Hot reload shows the result, and the next time you open the overlay it reads the reply, checks every annotation against the page and opens the list with the verdicts: applied, differs · font-size is 15px, expected 16px, missing or check by eye, next to what the agent said. Remove applied clears what landed; adjust the rest and save again.
Options
<Redlining
endpoint="/api/redlining" // where Save posts; false downloads the files
hotkey="Alt+R" // e.g. "Ctrl+Shift+K"
position="bottom-right" // bottom-left | top-right | top-left
theme="light" // or "dark"
maxAnnotations={15} // a warning shows at 10
screenshot // include screenshot.png with burned-in pins
framework="tailwind4" // tailwind4 | tailwind3 | css-modules | css; detected when omitted
/>
withRedlining(config, {
include: ['app', 'components', 'src'],
})
// app/api/redlining/route.ts — instead of the default re-export
import { createHandlers } from 'redlining/next/route'
export const { GET, POST } = createHandlers({ outDir: '.redlining', maxBytes: 16 * 1024 * 1024 })
The route refuses requests outside development, caps the body at 16 MB, and only ever
writes inside outDir, which must be inside the project.