comark-comments
Comments
Suggested edits and comments on a span of text, each with its note in a card in the margin, level with its line.
To log insign inClaudeThe button says Sign in.,
simplyClaude“Simply” tells a stuck reader it should have been easy. enter the email you signed up with and we’ll send you a link. The link expires after 15 minutes
onmaxIs 15 minutes enough when email is slow? Support gets tickets about expired links.ClaudeKeeping 15. The email has a resend button, and a longer-lived link is riskier.
atinuxAgreed. Mention the resend button in the email itself. and works onceCodexPeople reuse old links and hit an error..
In the event that you forgetIf you forgetClaudeShorter, same meaning. which email you used, ask a workspace admin
farnabazCan we link to the list of admins here?.
Your session lasts 30 days on this device. Signing out on one device doesn’t sign you out on the othersyour other devices.
To :suggestion[sign in]{from="log in" by="Claude" note="The button says Sign in."}, :suggestion[]{from="simply" by="Claude" note="“Simply” tells a stuck reader it should have been easy."} enter the email you signed up with and we’ll send you a link. The link expires after :comment[15 minutes]{#ttl by="@onmax" note="Is 15 minutes enough when email is slow? Support gets tickets about expired links."} :suggestion[and works once]{by="Codex" note="People reuse old links and hit an error."}.
:suggestion[If you forget]{from="In the event that you forget" by="Claude" note="Shorter, same meaning."} which email you used, ask :comment[a workspace admin]{by="@farnabaz" note="Can we link to the list of admins here?"}.
Your session lasts 30 days on this device. Signing out on one device doesn’t sign you out on :suggestion[your other devices]{from="the others" by="Codex"}.
:comment{reply="ttl" by="Claude" note="Keeping 15. The email has a resend button, and a longer-lived link is riskier."}
:comment{reply="ttl" by="@atinux" note="Agreed. Mention the resend button in the email itself."}
To log insign inClaudeThe button says Sign in., enter the email you signed up with. The link expires after 15 minutes and works once
ClaudePeople reuse old links and hit an error..
Signing out on one device doesn’t sign you out on the othersyour other devices.
To :suggestion[sign in]{from="log in" mark="pen" by="Claude" note="The button says Sign in."}, enter the email you signed up with. The link expires after 15 minutes :suggestion[and works once]{mark="pen" by="Claude" note="People reuse old links and hit an error."}.
Signing out on one device doesn’t sign you out on :suggestion[your other devices]{from="the others" mark="pen" by="Claude"}.Features
Section titled "Features"- A suggestion strikes the old words and inserts the new ones, as a redline or in a hand-drawn pen.
- A note opens a card in the margin, level with its line. Crowded cards stack down without opening gaps in the text.
- A reply written anywhere in the document joins its thread, and the bubble counts the messages.
- On a phone, a click on a bubble opens its card under the line. In print, the cards stay in a narrower margin.
- No script. An avatar is the author's image from
avatars, or their initial on a color picked from the name. - A screen reader hears "removed" and "added" around each change, and each card is a note that starts with its author.
Write :suggestion with the new text in its brackets and the old text in from:
To log insign inThe button says Sign in., enter your email.
To :suggestion[sign in]{from="log in" note="The button says Sign in."}, enter your email.Deletion
Section titled "Deletion"Use empty brackets, or none, with from to suggest removing words:
SimplyIt tells a stuck reader it should have been easy. Enter your email.
:suggestion[]{from="Simply" note="It tells a stuck reader it should have been easy."} Enter your email.Insertion
Section titled "Insertion"Leave out from to suggest adding words:
The link expires after 15 minutes and works oncePeople reuse old links..
The link expires after 15 minutes :suggestion[and works once]{note="People reuse old links."}.Use the note prop to say why. A suggestion with no note and no replies is only a redline, with no card and no bubble:
Signing out doesn’t sign you out on the othersyour other devices.
Signing out doesn’t sign you out on :suggestion[your other devices]{from="the others"}.Author
Section titled "Author"Use the by prop to name the author. It shows without a leading @, beside their initial or the image the plugin's avatars gives that name:
To log insign in
onmaxThe button says Sign in., enter your email.
To :suggestion[sign in]{from="log in" by="@onmax" note="The button says Sign in."}, enter your email.Use the mark prop to draw a suggestion with a hand-drawn pen, which strikes the old words and writes the new ones above them. An insertion hangs over a caret.
To log insign inThe button says Sign in., enter your email.
To :suggestion[sign in]{from="log in" mark="pen" note="The button says Sign in."}, enter your email.Note
Pen only draws short changes, with both sides 24 characters or fewer, on one line, and plain text with no code or links. Others draw as track.
Comment
Section titled "Comment"Write :comment with the words it's about in its brackets and a note:
The link expires after 15 minutes
onmaxIs 15 minutes enough when email is slow?.
The link expires after :comment[15 minutes]{by="@onmax" note="Is 15 minutes enough when email is slow?"}.Give a comment or suggestion an #id, then write :comment{reply="id"} with a note anywhere in the document. The reply joins that thread, and its empty paragraph is dropped:
The link expires after 15 minutes
onmaxIs 15 minutes enough?ClaudeKeeping 15. The email has a resend button..
The link expires after :comment[15 minutes]{#ttl by="@onmax" note="Is 15 minutes enough?"}.
:comment{reply="ttl" by="Claude" note="Keeping 15. The email has a resend button."}Examples
Section titled "Examples"Pen for the whole document
Section titled "Pen for the whole document"Pass mark to the plugin to draw every suggestion with the pen. A suggestion's own mark still wins:
import comarkUi from "comark-ui/html";
const { plugins, components } = comarkUi({ comments: { mark: "pen" } });Comments or critic
Section titled "Comments or critic"Critic reads CriticMarkup, inline marks with no notes that a host shows, accepts, or rejects with its mode. Comments add an author, a reason in the margin, and replies. Use critic to publish an edited draft, and comments for a review someone answers.
Accessibility
Section titled "Accessibility"Each deletion and insertion has hidden text, "removed: …" and "added: …", because most screen readers say nothing for <del> and <ins>. Each card has role="note" and starts with its author.
The bubble is a checkbox named by its thread, such as "2 comments on “15 minutes”". Tab reaches it. Space pins the card's highlight, or opens the card under the line on a narrow page.
Suggestion
Section titled "Suggestion"[new]The new text, in the brackets. Leave it empty, or leave out the brackets, for a deletion.
fromThe text it replaces. Leave it out for an insertion.
byThe author, shown without a leading @.
noteWhy, in plain text. Without it and without replies there is no card.
markDefault: trackHow it draws: track or pen.
#idThe id a reply points at.
Comment
Section titled "Comment"[anchor]The words it's about, in the brackets.
byThe author, shown without a leading @.
noteThe comment, in plain text.
#idThe id a reply points at.
replyThe #id of the comment or suggestion it answers.
byThe author, shown without a leading @.
noteThe reply, in plain text.
Options
Section titled "Options"markDefault: trackHow suggestions draw unless one sets its own mark: track or pen.
avatarsDefault: {}An image URL per author, by the name in by without @, in any case, such as { onmax: "https://github.com/onmax.png" }.
comments: false in comarkUi() turns it off.
Issues
Section titled "Issues"The plugin adds a problem with any suggestion, comment, or reply to meta.issues, as Validation describes:
reply to "tll" matches no comment or suggestion's `#id`, so it renders nothing. Did you mean `reply="ttl"`?It reports:
- A prop it doesn't know, such as
reasonfornote. - A suggestion with no new text and no
from, which renders nothing. - A suggestion whose
fromis its new text. - A
markother thantrackorpen. - A comment with nothing in its brackets, which renders nothing.
- A comment or a reply with no
note. - An
#idtwo threads share. Replies go to the first. - A
replythat matches no#id. It renders nothing. - A reply with text in its brackets. The text stays where it is.
- A comment or suggestion inside another one, or in a heading or a table cell, where it gets no card.
Each one has its own rule in Doctor.
Package
Section titled "Package"comark-ui brings comark-comments and registers it in comark-ui/html. To use it alone, install it and pass the renderer's components:
pnpm add comark @comark/html comark-commentsimport { createHtmlRenderer } from "@comark/html";
import comments, { components } from "comark-comments/html";
const render = createHtmlRenderer({ plugins: [comments({ mark: "track" })], components });Comments render only to HTML. Renderers covers the rest.
The cards and bubbles follow the shared tokens in Theming. The rules sit in a comark CSS layer, so any plain rule of yours wins, except the margin each block leaves on a wide page. That one is !important so a page's paragraph margins can't close it.
Styles
Section titled "Styles"The renderer adds the styles to every document with a comment or suggestion. To draw the same marks, bubbles, or cards outside a rendered document, such as review notes over a page, import them with yours:
import "comark-comments/style.css";Or inline the same styles from css:
import { css } from "comark-comments";comark-ui re-exports css as commentsCss from every entry. The pen's handwriting font isn't in them; the renderer adds it to a document with a pen mark.
Shared tokens
Section titled "Shared tokens"| Token | Used for |
|---|---|
--comark-background |
The cards and bubbles |
--comark-foreground |
The cards' text, and a highlighted bubble |
--comark-muted |
The bubbles' count |
--comark-border |
The cards' and bubbles' borders, and the line between replies |
--comark-ring |
A highlighted card or bubble, and the focus ring |
--comark-radius |
The cards' corners |
--comark-shadow |
A card while it is open beside the text |
--comark-font |
The cards' and bubbles' text |
Variables
Section titled "Variables"Set these on .cr or any ancestor, such as :root:
| Variable | Default | Used for |
|---|---|---|
--cr-del |
--comark-danger |
Deleted words, and the pen |
--cr-ins |
--comark-success |
Inserted words |
--cr-comment |
amber | A comment's highlight |
--cr-icon |
a grey speech bubble | The bubble's icon, as a CSS image |
An avatar without an image is the author's initial on a tint. The renderer sets --cr-hue on each one, a hue from 0 to 360 picked from the name, and the styles mix it into the page's colors. A host that draws its own avatars, such as for a comment it adds live, sets --cr-hue on each .cr-av too. Without it, the circle has no background and the letter takes the text color:
<span class="cr-av" style="--cr-hue: 145" aria-hidden="true">M</span>Classes and attributes
Section titled "Classes and attributes"| Selector | Is |
|---|---|
.cr |
The root around the document's blocks, when any card goes in the margin |
.cr-sg |
A suggestion, with data-mark (track or pen) and data-kind (replace, delete, or insert) |
.cr-cm |
A comment's anchor, a <mark> |
.cr-bub |
The bubble, a <label> for the hidden .cr-tg checkbox |
.cr-card |
The card |
.cr-entry |
One message in a card: the note, then each reply |
.cr-av |
An avatar: an <img>, or a <span> with the author's initial on a tint of its --cr-hue |
Example
Section titled "Example"Blue comments:
:root {
--cr-comment: #3b82f6;
}