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.
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.
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:
alttext,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
- Download the zip from the release page and unzip it.
- Drag Nudge into your Applications folder.
- 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.
