Guide
The overlay lives in a shadow root on top of your page. While it is inactive it registers only the hotkey and captures no pointer events; your app behaves exactly as before.
Toolbar and keys
| Key | Action |
|---|---|
| Alt+R |
Toggle the overlay (hotkey option). Matched on the physical key, so it
works on macOS where Option+R types ®.
|
| S / D / M / T | Select / Draw / Move / Tweak mode |
| [ / ], ⌥+scroll | Walk the hovered selection up / down the ancestor chain |
| ⇧+click | Add another element to the open note |
| Handles, ⌥ drag, ⌥⇧ drag | Tweak: resize, set padding, set margin on that edge |
| Arrows, ⇧ arrows, drag | Tweak: nudge by 1px / 10px / freely (exported as a visual nudge) |
| ⌘+Z | Tweak: undo the last change |
| ⌥ hover | Tweak: measure the distance to another element |
| L | Annotation list panel |
| Width select (375 / 768 / 1280) | Open the page in a device frame of that width; annotations made inside sync into this session |
| Move button | Cycle the toolbar and the pen button through the four corners; remembered per browser |
| Camera button | Include a screenshot with pins when saving (on by default) |
| ⌘+⇧+C | Copy the Markdown prompt to the clipboard |
| ⌘+⏎ | Save to .redlining/ |
| Trash button | Clear the session, from the list panel's header (asks first) |
| ? | In-package help: keys, the loop, links to these pages |
| Esc | Close the popover, then the panel, then the overlay |
On Windows and Linux Ctrl stands in for ⌘.
Select — change or remove something that exists
Hover to outline the smallest host element under the cursor. The badge shows the innermost
component that rendered it, the file and line, and the tag. Often you mean the whole card
rather than the <span> inside it: press [ until the outline
covers what you mean. Click to drop a pin and open the note.
The note popover offers Change and Remove. Write what should be different; the anchor already says where. ⏎ saves, ⇧+⏎ inserts a newline, Esc cancels.
Draw — add something new in a region
Press D, then drag a rectangle where the new thing belongs. On release Redlining resolves the container: the deepest decorated element whose box covers at least 60 % of what you drew, otherwise the nearest decorated ancestor of the centre point. The popover says Add inside FilterPanel and the export records a position such as after child 2 · full width · ≈ 220 px tall.
The component-hint chips (Table, Form, Button, Card, Modal, Nav, List, Chart) prepend a keyword to your note; free text remains the primary instruction. The box is stored relative to the container, so it survives scrolling and resizing.
Move — put something somewhere else
Press M. Click the element to move; it gets a dashed outline. Click the
destination; the popover asks before, after or inside and takes
a note. The pin sits on the source and a →N marker on the target. The export
writes a From and a To line, so nobody has to describe DOM positions in
prose.
Multi-select — one note, several elements
With a select note open, ⇧+click other elements to add them to the same annotation ("make all of these the same height"). The popover shows +N, every element gets a pin with the same number, and the export lists the anchors under one note.
Tweak — change values directly, export the numbers
Press T and click an element. The page updates live while you work; nothing is
written to your code. When you press Done, the annotation records every change as
before → after in computed pixels, next to the element's class list, so the agent
can translate "16px" into text-base or a token rather than an inline style.
-
Inspector. Steppers for font size, line height, tracking, width,
height, radius, opacity, padding and margin per side; weight chips; a text field when
the element has a single text node and is not a controlled input; text, background and
border colours with a picker and a list of your own
:roottokens; gap, justify and align chips on flex and grid containers; Hide; Undo; Reset. Sections start collapsed and show their values in one line (margin 10 0 20 0, 15 · 600 · 22 · normal); click a heading to open it. - Where a value comes from. Every field carries a caption with the winning rule: from .text-lg, var(--leading) · .prose, inherited from <section> .prose, inline style. A width or height nobody sets is labelled auto · from grid (or flex, block) in the warning colour: you can still change it, but the export says the size is set by the parent's layout and that changing the layout is the better fix. When the new value matches a single-class rule in your own stylesheet, the export names it (class text-base → text-lg) so the agent swaps a class instead of inventing a number. Snap to scale (on by default) makes the plus and minus buttons step through those classes (14 → 16 → 18 → 20) rather than by a pixel; turn it off for free values.
- Handles. Drag a corner or edge to resize (⇧ keeps the ratio). ⌥-drag an edge to set that side's padding, ⌥⇧-drag for margin.
- Nudge. Drag the element or use the arrow keys (⇧ for 10px). This is previewed with a transform, and the export says so explicitly: "visual nudge: +12px right — implement as spacing or alignment, never ship a transform".
- Measure. Hold ⌥ and hover another element to see the distance between the two; it is a ruler and records nothing.
- Device frame. The toolbar's width select (375, 768, 1280) opens the same page in an iframe of that width over the current one. It is a real viewport: media queries fire, fixed bars and bottom tab bars behave as on a phone. The overlay inside the frame opens by itself and shows a width badge; annotate there as usual, the notes land in the same session, and each carries Applies at: ≤ 375px (made in a 375px device frame). Close the frame with the × or by choosing Full.
-
Before/after. With the camera on, the second toggle also saves
screenshot-before.pngwith every preview reset.
Previews survive a reload and hot reload; deleting the annotation or clearing the session restores the element. The note is optional for a tweak, the numbers usually say enough.
List panel
L opens a panel on the right with every annotation: number, action, component
and file, and the note clamped to two lines. The chevron expands a row to the full anchor
(file and line, owner chain, usage site, element text), the target of a move or the box of
an add, and the complete note. Click the note to edit it inline (⏎ saves,
Esc cancels); the × deletes and renumbers the rest. The toolbar moves aside
while the panel is open. Sessions are kept per route; when other routes in this browser
have annotations, the panel lists them at the bottom and Save and Copy include them, each
under its own # Redlining — /route heading, so one /redline run
covers the whole pass. The × next to a route clears it.
Settings
The gear in the toolbar opens the settings. Styling shows which idiom was
detected from the page (Tailwind 4 from its theme tokens, Tailwind 3 from the preflight
variables, CSS Modules from hashed class names, otherwise plain CSS) and lets you override
it; the framework prop sets it in code. It decides how tweak values map to
classes: under Tailwind, padding-left 12 → 24 exports as
add class pl-6 even if pl-6 is not used anywhere yet, off-scale
values become pl-[13px], colours use their --color-* token
(bg-brand-500), and the steppers snap to the theme scale. The export header
states the idiom and the footer tells the agent how to apply it.
Snap to scale sets the Inspector's default, and
Include other routes controls the multi-route export.
Copy and Save
Copy prompt puts the Markdown spec on the clipboard, for pasting into any
agent. Save posts the session to the endpoint, which writes
annotations.md, annotations.json and, with the camera on,
screenshot.png with the pins burned in. Pins in the screenshot carry the same
numbers as the Markdown headings, so "pin 3" is unambiguous. With the camera on,
every annotation also gets crop-N.png, the element as it looks now with a
small margin, so the agent sees exactly the thing being discussed.
Reference images. Paste or drop a mockup into the note (up to three per
note; shrunk to 1600px on the long edge). It shows as a thumbnail in the popover and the
panel, survives reloads with the session, and is saved as ref-N-i.jpg; the
Markdown line Reference: .redlining/ref-3-1.jpg — match this tells the agent to
look at it. "Copy prompt" cannot carry images, so it only counts them.
The screenshot is rendered in the browser and skipped with a toast if it would exceed 7 MB; the route accepts bodies up to 16 MB.
Verify — did the agent's edit land?
The /redline command ends by writing .redlining/reply.md, one
line per annotation:
## 3 · done — text-sm → text-lg (components/toolbar.tsx:31) (or
partial, skipped). When you open the overlay after that, it reads the
reply through the route's GET, removes its previews for a moment, and checks
every annotation against the page as it is now: each tweak's target values against the
computed styles, edited text against the DOM, a hidden element against its display, a
removed element against its absence. The list opens with a verdict per row —
applied, differs · font-size is 15px, expected 16px, missing,
or check by eye for notes and nudges — beside what the agent reported.
Remove applied drops what landed and keeps the rest for a second round; the
magnifier in the panel header runs the check again at any time. Routes created before
0.6.0 export only POST; add GET (npx redlining init
says so) or the reply is simply not read.
Sessions
A session is all annotations for one route. It is stored in localStorage per
route, so a reload or a /redline run does not lose it; pins re-attach to
their elements after hot reload by the stamped file and line, then by CSS selector. Clear
it with the trash button when the batch has landed.
Sessions are capped at 15 annotations, with a warning at 10. Larger batches produce worse results from the agent than two smaller ones.
How anchors resolve
| Situation | Anchor | What the export says |
|---|---|---|
The element carries data-rl |
exact | file:line of the opening tag |
| Only an ancestor carries it | ancestor | the ancestor's file:line plus locate the child by selector |
| Nothing decorated (third-party UI, portals) | selector-only | unresolved — locate by selector and text |
Anchors from a reusable component also carry their usage site: the nearest
decorated ancestor in another file and the branch's position among its children, so
"instance 2 of 3 in <section> · app/page.tsx:41" tells the second card
apart from the first and third even though all three are written at
card.tsx:33.
Components are never decorated, only host elements like <nav> and
<button>: an unknown prop on your components would leak into prop
spreads and warnings. Component names come from React's owner chain at runtime instead,
which is why server-only subtrees show an empty owner list but still resolve exactly.