Chris
Keene

Free tool

Nudge

A small Mac app for changing the words and the layout of a self-contained HTML deck or report, without opening a code editor and without the file being quietly destroyed on the way through.

Free and open source (MIT). About 1 MB, because there is no bundled runtime in it, just AppKit and WebKit. Apple silicon Macs on macOS 13 or later, and it is not signed, so the first launch needs a trip to System Settings. Details below.

The file is finished, and then one word is wrong

A lot of what I build ends up as a single self-contained HTML file: a deck, a report, a one-pager that has to survive being emailed around. That format earns its keep. It opens anywhere, it needs no server, and unlike a PDF it is still real structure underneath, so it works properly for anyone using a screen reader.

The catch comes afterwards. Someone reads it and wants a different verb in the second bullet. A logo sits two pixels high in a row of four. The subtitle would be better one size down. Each of these is ten seconds of work and none of them is worth opening a code editor for, let alone going back to the thing that generated the file and asking for another pass.

Why it doesn't just use the browser's copy of the page

Almost every "edit HTML visually" tool does the same thing: set contenteditable, let you type, then read back document.documentElement.outerHTML. For these files that is not a shortcut, it is a demolition.

A deck runs its own JavaScript. Its navigation script sets a slide counter, writes a breadcrumb, and hides every slide that is not the current one. Serialise the DOM after that has run and you bake the current state into the file permanently: save while you are looking at slide three and every other slide gets a hidden attribute. The counter freezes at "3 / 17". Inline styles the script set become part of the document. The deck is gone, and it looked fine at the moment you saved it.

The usual way · serialise whatever the browser is holding Your file as you wrote it Browser runs it the deck's own JS DOM read back outerHTML Deck broken slide 3 baked in Nudge · splice into the source, never serialise Your file as you wrote it Source scanned byte ranges found Edits spliced in back to front File intact byte for byte
The edits are spliced back to front, so each one lands before the offsets after it can shift. Everything you did not touch survives exactly: the CSS, the JavaScript, the indentation, the comments, the attribute order, even which entities were spelled out.

So Nudge never serialises a DOM. It scans the original file with a tokeniser that tracks offsets, recording the exact character range of every run of readable text and skipping scripts, styles, comments and markup. It renders the real, unmodified file in a WebKit view alongside, purely so that clicking a word can be matched to a position in the source. The two sequences are aligned, tolerating text the deck's own JavaScript invents along the way. On save, only your changes are written.

Layout changes go on the element, not in the stylesheet

Every layout adjustment is written as an inline declaration on the one element you selected. Nothing is added to the stylesheet, so widening a single card can never quietly restyle the other fourteen slides that happen to share its class.

Two modes, one key

⌘E switches between them, and the selection comes with you, so you can fix a word and then give the box it sits in a little more room without hunting for it again.

  • Wording. Click any text in the live preview and type. Body copy, headings, list items, table cells, buttons and captions, but also the parts nobody can see: alt text, aria-label, placeholders, the page <title> and its meta description. Link destinations sit behind their own filter so they stay out of the way until you want them. You can also set how the selected words look: size, alignment, weight and colour.
  • Layout. Click any element and adjust its width as a percentage of its container, so it still responds; its height, padding, the space above and below it, and the gap between a container's children. You can shift it, reorder it among its siblings, duplicate it or delete it.

Moving one thing without moving its neighbours

This one took the longest to get right, and the naive answers are all wrong. These files are built from grid and flex containers, so nothing has a coordinate. Position is a consequence of flow.

Absolutely positioning things so they could be dragged would break the responsive and print behaviour, and it would decouple the visual order from the DOM order, which is a WCAG 1.3.2 failure. Writing a reading order that no longer matches what you see is not a trade I am willing to make in a tool I would use on an accessibility deck. Padding and margins do not work either: they change an element's own box, so the row re-flows and everything beside it shifts too. That is exactly wrong when an icon is sitting two pixels high in a flex row.

So Nudge writes a paint-time offset instead. The item is drawn somewhere else while the layout carries on as though it had not moved. Nothing beside it shifts, the file still responds and still prints, and the order a screen reader follows is untouched. Which technique it uses depends on what the browser actually resolved for that element, because a translate on a plain inline element is silently ignored, and inside an SVG the rule inverts completely.

It doesn't say <dd> at anyone

The interface never shows you markup. Tags are translated into words the person editing would actually use: Title, Paragraph, Value, Button, Image description, Screen-reader label. The real markup is still there in a tooltip for anyone who wants it. Two consequences worth spelling out:

  • The list is grouped by slide, slides first. Source order would open on the skip link and the navigation rail, which is a baffling first thing to meet. Everything that lives around the slides is gathered at the end under "Page furniture".
  • A sentence stays a sentence. Inline markup splits one sentence into several separate runs, which is correct for editing and nonsense to read. The list shows the whole sentence as one row and tells you how many pieces it has. Rewrite part of it and the emphasis on the parts you left alone survives.

Its own interface meets WCAG too

AppKit's own secondaryLabelColor and tertiaryLabelColor measure 3.8:1 and 1.9:1 on a standard window background. That is fine by Apple's guidelines and a 1.4.3 failure for the small text this app uses them for. systemOrange is 1.9:1 and unusable for a warning.

So the app has its own palette, and the test suite measures every colour against the surface it actually sits on, in both light and dark. It also sweeps every label in the window and fails on any whose text needs more room than it has been given, because contrast and overflow checks both pass perfectly happily on text jammed against its frame. At one point a compliant colour was being specified and a non-compliant one was reaching the screen, which is why the suite now measures rendered pixels rather than trusting what was asked for.

Nothing is written until you say so

  • Review changes (⌘R) shows every pending edit, old against new, before a single byte is written.
  • Every save leaves a timestamped copy of the previous version in ~/Library/Application Support/Nudge/Backups.
  • Smart quotes, smart dashes and text substitution are off in the editor, so nothing silently turns your hyphen into an em dash.
  • Deleting asks first and tells you what is inside the thing you are about to remove.

Installing it

  1. Download the zip from the release page and unzip it.
  2. Drag Nudge into your Applications folder.
  3. Double-click it. macOS will refuse, and say it cannot check it for malicious software. Open System Settings ▸ Privacy & Security, scroll to the bottom, and click Open Anyway next to Nudge. You only do this once; it opens normally from then on.

That step exists because the app is signed ad hoc rather than with a paid Apple developer certificate. If you would rather not take my word for any of it, the source is all there and ./build.sh builds it with nothing but the Xcode Command Line Tools: no project file, no dependencies.

Open a file with ⌘O or drag it onto the app, and press ⌘? for the built-in guide. There is a fuller one attached to the release.

The honest caveats

  • This is a personal project and a work in progress. It is built in my own time, it has rough edges, and it is not a product. I use it constantly and it does the job, which is a different claim from it being finished.
  • Apple silicon Macs only, macOS 13 or later, and not notarised. See above.
  • It is for self-contained files. One HTML file that carries its own CSS and JavaScript is the thing it was built for. Point it at a page that pulls half its content from somewhere else at runtime and there is much less for it to hold on to.
  • Read the review sheet before you save. It is careful, it keeps backups, and it still only ever proposes what you asked it to do. Look at the diff.
  • It is a finishing tool, not an authoring one. If you find yourself making thirty changes, the file wanted regenerating rather than nudging. It is for the last five.

If you try it and something breaks, or it mangles a file in a way I have not thought of, I would genuinely like to know. Issues on the GitHub repo are the easiest way, or there is a contact link on the front page.