
Marginalius is an Obsidian plugin for Tufte/Cornell-style side notes. On a wide screen the notes sit in the page margins. When space is tight, they turn into small boxes embedded in the text.
You write a note once with an inline marker, and the plugin picks the layout from the width of the editor pane.
%%> text %% goes to the left margin and %%< text %% to the right.%% … %%), so if Marginalius is disabled, Obsidian treats the notes as ordinary comments and leaves no broken markup.Option+Space on macOS, or pasted from rich text) are replaced at render time, so phrases wrap at real word boundaries. Your note is never changed.Put a marker inline in your text:
%%> Result: only the mysql-backup pod can assume that role and write to S3;
other pods cannot. So yes: it's access control and least privilege. %%
Your main body text goes here. On a wide screen the note above is rendered
in the left margin next to this paragraph.
%%< This note is pulled into the right-hand margin instead. %%
| Marker | Side |
|---|---|
%%> text %% |
Left margin |
%%< text %% |
Right margin |
%% (> or <) sets the side.Select a rendered note in the editor (Live Preview or Source mode) to edit it. It switches back to its source, and the cursor lands at the start of the text, right after the %%> or %%< marker. Links inside a note still open normally, and right-clicking a note leaves the cursor where it was.

Move the cursor out of the note and it renders again.
Marginalius is in Obsidian's community plugin directory. It needs Obsidian 1.13 or later.
Download main.js, manifest.json and styles.css from the latest release and copy them into your vault's plugin folder:
cp main.js manifest.json styles.css "<Vault>/.obsidian/plugins/marginalius/"
Then reload Obsidian and enable the plugin in Settings → Community plugins.
Settings → Community plugins → Marginalius
| Setting | Description | Default |
|---|---|---|
| Margin note width | Maximum width of a note in the margin. A short note takes less. | 200px |
| Gap to text | Gap between a margin note and the text. | 20px |
| Pane edge gap | Space kept between a note and the edge of the pane, such as a sidebar, on both sides. A note that would come closer becomes a box in the text. | 30px |
| Box width | Maximum width of a note shown as a box in the text. A short note gets a box that fits its text. | 200px |
Each value is a CSS length, such as 200px or 12em. A number without a unit (200) isn't valid CSS: the field shows an error and the value isn't saved. An empty field means the default. The settings use Obsidian's declarative settings, so they also show up in the settings search.
The values go into the CSS custom properties --marginalius-width, --marginalius-margin, --marginalius-pane-gap and --marginalius-intrusive-width, on the main window and on any popout windows.
The code is split into small modules under src/:
main.ts: the plugin lifecycle. It loads settings, sets the CSS variables, and registers everything else.settings.ts: the settings interface, defaults and settings tab. The tab declares its fields with getSettingDefinitions() and saves through the plugin, so the CSS variables and note placement update as you type. Invalid values saved by an older version are reset to the default on load.parser.ts: a regex parser with no Obsidian dependencies. It finds every %%> … %% and %%< … %% in a string, skipping inline code and fenced code blocks, and returns each match with its document offsets, side and normalized content. Normalizing replaces non-breaking characters (U+00A0, U+202F, U+2007, the word joiner/ZWNBSP, and the non-breaking hyphen) with ordinary ones so narrow notes wrap. Your note is never changed.note.ts: builds the note element and renders its Markdown with Obsidian's MarkdownRenderer. Both views use it.placement.ts: decides for each note whether it fits in the margin. A ResizeObserver measures each note in the margin layout (how far its outer edge reaches from the column), again whenever a setting changes, and watches the pane. When the free space beside the column gets smaller than that, the note gets the marginalia-intrudes class and becomes a box. It also records the free space as --marginalius-room-left and --marginalius-room-right, and measures list indents so left notes line up with the column edge.live-preview.ts: a CodeMirror StateField that decorates the editor. It is a state field because notes can span several lines. Each marker is hidden where it is, and the note is drawn as a MarginaliaWidget at the start of the marker's line, so a box floats from the top of its paragraph rather than from wherever the marker sits. When the cursor or selection touches a marker, the raw source shows so you can edit it, and selecting a widget puts the cursor at the start of the note's text.reading-view.ts: a MarkdownPostProcessor for Reading view. Obsidian strips %% … %% comments before rendering, so the processor gets the raw Markdown from getSectionInfo() and finds the notes with the shared parser. Each note goes into the list item it belongs to (Obsidian renders a whole list as one block), or else into its paragraph. If a full re-render passes no source text, the processor reads the file instead. Obsidian only hides a comment inside one paragraph, so a note that crosses a blank line would show as plain text; the processor renders those blocks again without the note.commands.ts: the Insert left marginalia and Insert right marginalia commands.styles.css: all the presentation. The box in the text (floated, rounded, liquid glass) is the default. Its rim is a 1px gradient ring on ::before, cut out with a mask. While a note fits in the margin, it is positioned absolutely beside the column and centered on its block: the editor line, list item or paragraph.A box's outer margin is negative by the free space beside the column:
margin-right = -(free space on the right) /* left notes use margin-left */
The free space already leaves out the Pane edge gap, so a box that has just stopped fitting keeps its outer edge where the margin note's was, that gap away from the pane edge, and only its inner part reaches into the text. As the pane narrows, the free space shrinks and the box moves further in until it sits fully inside the column. Reading view adds a little extra separation in the margin (--marginalius-edge) because its text sits slightly closer to the gutter than the editor's.
Note: The margin layout needs Readable line length turned on (the default), because that setting creates the side gutters. With it off, notes are boxes at any width.
Requirements: Node.js 18+ and npm.
# install dependencies
npm install
# development build with watch
npm run dev
# production build (type-check + bundle)
npm run build
# tests (parser, Live Preview decorations, Reading view placement, insert command, margin/box placement)
npm test
# lint with Obsidian's plugin rules (eslint-plugin-obsidianmd)
npm run lint
npm run build type-checks with tsc and bundles src/main.ts into main.js with esbuild (see esbuild.config.mjs). Tests use Node's built-in test runner with a small stub of the obsidian API (test/obsidian-stub.ts), because the real API only exists inside the app. To try a local build, copy it into a vault as in Install manually.
Releases are built by .github/workflows/release.yml: push a tag equal to the manifest version, and Obsidian's obsidian-workflows action builds main.js, attests it and styles.css, and creates a draft release to publish.
Marginalius runs locally. It makes no network requests, collects no telemetry, and never changes your notes on disk. The non-breaking-space replacement only affects what is rendered on screen.
GPL-3.0, see LICENSE.