惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
T
The Exploit Database - CXSecurity.com
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
T
Tailwind CSS Blog
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
WordPress大学
WordPress大学
Hugging Face - Blog
Hugging Face - Blog
IT之家
IT之家
PCI Perspectives
PCI Perspectives
腾讯CDC
酷 壳 – CoolShell
酷 壳 – CoolShell
Recent Commits to openclaw:main
Recent Commits to openclaw:main
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
N
News and Events Feed by Topic
博客园 - 三生石上(FineUI控件)
Help Net Security
Help Net Security
雷峰网
雷峰网
有赞技术团队
有赞技术团队
Last Week in AI
Last Week in AI
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
量子位
GbyAI
GbyAI
O
OpenAI News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
AI
AI
S
Security Affairs
F
Fortinet All Blogs
L
LINUX DO - 最新话题
博客园 - 【当耐特】
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
W
WeLiveSecurity
Security Latest
Security Latest
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Tenable Blog
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
Simon Willison's Weblog
Simon Willison's Weblog
Cyberwarzone
Cyberwarzone
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
CERT Recently Published Vulnerability Notes
Engineering at Meta
Engineering at Meta
P
Palo Alto Networks Blog
博客园 - 叶小钗
T
Threatpost
H
Heimdal Security Blog
Microsoft Security Blog
Microsoft Security Blog

Tech – Thoughts From Eric

In situ Syntax Highlighting in macOS Applications Like Keynote Accessible (I Think) Split-Cell Table Headers Canvas-ing the Web Targeting by Reference in the Shadow DOM Custom Asidenotes Bookmarklet: Load All GitHub Comments (take 2) No, Google Did Not Unilaterally Decide to Kill XSLT To Infinity… But Not Beyond! Infinite Pixels Masonry, Item Flow, and… GULP? CSS Naked Day 2025 CSS Wish List 2025 Announcing BCD Watch Design for Real Life News!
Parenthetical Asidenotes
Eric Meyer · 2025-10-28 · via Tech – Thoughts From Eric

It’s not really a secret I have a thing for sidenotes, and thus for CSS anchored positioning.  But a thing I realized about myself is that most of my sidenotes are likely to be tiny asides commenting on the main throughline of the text, as opposed to bibliographic references or other things that usually become actual footnotes or endnotes.  The things I would sidenote currently get written as parenthetical inline comments (you know, like this).  Asidenotes, if you will.

Once I had realized that, I wondered: could I set up a way to turn those parenthetical asides into asidenotes in supporting browsers, using only HTML and CSS?  As it turns out, yes, though not in a way I would actually use.  In fact, the thing I eventually arrived at is pretty terrible.

Okay, allow me to explain.

To be crystal clear about this, here’s how I would want one of these parenthetical asides to be rendered in browsers that don’t support anchored positioning, and then how to render in those that do (which are, as I write this, recent Chromium browsers and Safari Technology Previews; see theanchor() MDN page for the latest):

A parenthetical sitting inline (top) and turned into an asidenote (bottom).

My thinking is, the parenthetical text should be the base state, with some HTML to flag the bit that’s an asidenote, and then CSS is applied in supporting browsers to lay out the text as an asidenote.  There is a marker pair to allow an unambiguous association between the two, which is tricky, because that marker should not be in the base text, but should appear when styled.

I thought for a minute that I would wrap these little notes in <aside>s, but quickly realized that would probably be a bad idea for accessibility and other reasons.  I mean, I could use CSS to cast the <aside> to an inline box instead of its browser-default block box, but I’d need to label each one separately, be very careful with roles, and so on and so on.  It was just the wrong tool, it seemed to me.  (Feel free to disagree with me in the comments!)

So, I started with this:

<span class="asidenote">(Feel free to disagree with me in the comments!)</span>

That wasn’t going to be enough, though, because I can certainly position this <span>, but there’s nothing available to leave a maker behind when I do!  Given the intended result, then, there needs to be something in the not-positioned text that serves in that role (by which I mean a utility role, not an ARIA role).  Here’s where my mind went:

<span class="asidenote">(by which I mean a utility role, not an ARIA role)</span><sup></sup>

The added <sup> is what will contain the marker text, like 1 or a or whatever.

This seemed like it was the minimum viable structure, so I started writing some styles.  These asidenotes would be used in my posts, and I’d want the marker counters to reset with each blog post, so I built the selectors accordingly:

@supports not (anchor-name: --main) {
	#thoughts article .asidenote + sup {
		display: none;
	}
} 
@supports (anchor-name: --main) {
	#thoughts {
		anchor-name: --main;
	}
	#thoughts article {
		counter-reset: asidenotes;
	}
	#thoughts article :is(.asidenote::before, .asidenote + sup::before) {
		content: counter(asidenotes);
	}
}

So far, I’ve set a named anchor on the <main> element (which has an id of thoughts) that encloses a page’s content, reset a counter on each <article>, and inserted that counter as the ::before content for both the asidenotes’ <span>s and the <sup>s that follow them.  That done, it’s time to actually position the asidenotes:

	#thoughts article .asidenote {
		counter-increment: asidenotes;
		position: absolute;
		anchor-name: --sidenote;
		top: max(calc(anchor(--sidenote bottom, 0px) + 0.67em), anchor(top));
		bottom: auto;
		left: calc(anchor(--main right) + 4em);
		max-width: 23em;
		margin-block: 0.15em 0;
		text-wrap: balance;
		text-indent: 0;
	}

Here, each class="asidenote" element increments the asidenotes counter by one, and then the asidenote is absolutely positioned so its top is placed at the larger value of two-thirds of an em below the bottom of the previous asidenote, if any; or else the top of its implicit anchor, which, because I didn’t set an explicit named anchor for it in this case, seems to be the place it would have occupied in the normal flow of the text.  This latter bit is long-standing behavior in absolute positioning of inline elements, so it makes sense.  I’m just not sure it fully conforms to the specification, though it’s particularly hard for me to tell in this case.

Moving on!  The left edge of the asidenote is set 4em to the right of the right edge of --main and then some formatting stuff is done to keep it balanced and nicely sized for its context.  Some of you will already have seen what’s going to happen here.

An asidenote with some typographic decoration it definitely should not have in this context.

Yep, the parentheses came right along with the text, and in general the whole thing looks a little odd.  I could certainly argue that these are acceptable design choices, but it’s not what I want to see.  I want the parentheses to go away when laid out as a asidenote, and also capitalize the first letter if it isn’t already, plus close out the text with a full stop.

And this is where the whole thing tipped over into “I don’t love this” territory.  I can certainly add bits of text before and after an element’s content with pseudo-elements, but I can’t subtract bits of text (not without JavaScript, anyway).  The best I can do is suppress their display, but for that, I need structure.  So I went this route with the markup and CSS:

<span class="asidenote"><span>(</span>by which I mean a utility role, not an ARIA role<span>)</span></span><sup></sup>
	#thoughts article .asidenote span:is(:first-child, :last-child) {
		display: none;
	}

I could have used shorter elements like <b> or <i>, and then styled them to look normal, but nah.  I don’t love the clutter, but <span> makes more sense here.

With those parentheses gone, I can uppercase the the first visible letter and full-stop the end of each asidenote like so:

	#thoughts article .asidenote::first-letter {
		text-transform: uppercase;
	}
	#thoughts article .asidenote::after {
		content: ".";
	}

Then I do a little styling of the asidenote’s marker:

	#thoughts article .asidenote::before {
		content: counter(asidenotes);
		position: absolute;
		top: -0.4em;
		right: calc(100% + 0.25em);
	}
} /* closes out the @supports block */

…and that’s more or less it (okay, yes, there are a few other tweaks to the markers and their sizes and line heights and asidenote text size and blah blah blah, but let’s not clutter up the main points by slogging through all that).  With that, I get little asides that are parenthetical in the base text, albeit with a bunch of invisible-to-the-user markup clutter, that will be progressively enhanced into full asidenotes where able.

There’s an extra usage trap here, as well: if I always generate a full stop at the end, it means I should never end my asidenotes with a question mark, exclamation point, interrobang, or other sentence-ending character.  But those are things I like to do!

So, will I use this on meyerweb?  Heck to the no.  The markup clutter is much more annoying than the benefit, it fumbles on some pretty basic use cases, and I don’t really want to go to the lengths of creating weird bespoke text macros  —  or worse, try to fork and extend a local Markdown parser to add some weird bespoke text pattern  —  just to make this work.  If CSS had a character selector that let me turn off the parentheses without needing the extras <span>s, and some kind of outside-the-element generated content, then maybe yes.  Otherwise, no, this is not how I’d do it, at least outside this post.  At the very least, some JavaScript is needed to remove bits of text and decide whether to append the full stop.

Given that JS is needed, how would I do it?  With custom elements and the Light DOM, which I’ll document in the next post.  Stay tuned!