Semantic Markup for Callouts

A Not-Quite-RFC for Admonitions in HTML

Abstract

As the name may imply, callouts (sometimes called “admonitions”) call out a specific text from the content. They are meant to draw priority attention to specific text. Often you see accompanying labels such as: tip, note, attention, warning, caution, danger, important. Callouts are very common to see in technical writings & documentation—and they show up in most lightweight markup syntaxes. However, despite their commonality, they are not supported first-class by HTML & many of the existing options aren’t well-suited for the task for accessibility or standardization that would assist in the rendering in TUI browsers, reader mode, etc. What we’ll look at are some callouts in the wild along with their lightweight markup counterpart, as well as propose something better.

Important

This isn’t quite an RFC, but if anyone would like to co-write one with me & iron out all of its technical holes, please see the contact info in the footer of the document.

Living Grove of Callout Choosings

The callout cat has been skinned many times, so lets look into the pickings in the wild.

AsciiDoc admonitions (as output by Asciidoctor)

Our input

[WARNING]
====
Wolpertingers are known to nest in server racks.
Enter at your own risk.
====

leads to our output

<div class="admonitionblock warning">
	<table>
		<tbody>
			<tr>
				<td class="icon">
					<i class="fa icon-warning" title="Warning"></i>
				</td>
				<td class="content">
					Wolpertingers are known to nest in server racks.
					Enter at your own risk.
				</td>
			</tr>
		</tbody>
	</table>
</div>
Pros
  • <table> has some semantic meaning for tabular data, it’s not as strong as you’d think.

    The table element represents data with more than one dimension, in the form of a table.

    —Living HTML Stardard (at time of post), <https://html.spec.whatwg.org/multipage/tables.html#the-table-element>

    For those of us around HTML for a while, there was a point where almost everything was made of tables because of the lack of CSS features for the longest time. The specification did not say that it needed to be specifically tabular data. As such, with the callout title text being such a common pattern, you could see it as column 1 w& its accompanying body as column 2—which is semantic enough.

  • <table> renders nicely in TUI browsers & reader mode for the purpose of calling attention by putting the default table borders around it.
Cons
  • If looking at <table> elements as semantic for tabular data, this breaks expectations. Nothing would be indicated to screen reader or general scraper the importance of the content.

reStructuredText admonitions (as output by Docutils’s html5)

Our input

.. DANGER::
	Beware killer rabbits!

outputs as

<aside class="admonition danger">
	<p class="admonition-title">!DANGER!</p>
	<p>Beware killer rabbits!</p>
</aside>
Pros
  • For “tip” callouts specifically this markup seems just fine.
  • Tries to use semantic HTML
Cons
  • <aside> has an implicit complementary role. This role implies that the content is expected to work as standalone content & only relates to the main content. In the case of !DANGER!, this message is very much for a part of the text & requires the body to be make sense.
  • <p> for a title is not the right markup.

Obsidian’s proprietary Markdown fork for Callouts

Our input

> [!info]
> Here's a callout block.
> It supports **Markdown**, [[Internal link|Wikilinks]], and [[Embed files|embeds]]!

outputs as

<div data-callout-metadata="" data-callout-fold="" data-callout="info" class="callout">
	<div class="callout-title">
		<div class="callout-icon">
			<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-info">
				<circle cx="12" cy="12" r="10"></circle>
				<line x1="12" y1="16" x2="12" y2="12"></line>
				<line x1="12" y1="8" x2="12.01" y2="8"></line>
			</svg>
		</div>
		<div class="callout-title-inner">Info</div>
	</div>
	<div class="callout-content">
		<p>
			Here's a callout block.<br>
			It supports <strong>Markdown</strong>, <a data-tooltip-position="top" aria-label="Internal links" data-href="Internal links" href="https://help.obsidian.md/Linking+notes+and+files/Internal+links" class="internal-link" target="_blank" rel="noopener">Wikilinks</a> and <a data-tooltip-position="top" aria-label="Embedding files" data-href="Embedding files" href="https://help.obsidian.md/Linking+notes+and+files/Embedding+files" class="internal-link" target="_blank" rel="noopener">embeds</a>!<br>
		</p>
	</div>
</div>
Pros
  • none
Cons
  • Muddies the sigil/symbol for blockquotes in Markdown by making the > pull twin duties in the parser. Other Markdown parsers, such as CommonMark, will turn this into a unsemantic <blockquote> which is bad.
  • Rendering the container as a <div> gives this element no semantics.
  • While better than a <p> semantically, the title still lacks a title-like role or semantic.
  • Does not get any special rendering for TUI browsers & reading mode

MDN’s Markdown fork for Notecards

Our input

> **Note:** Don't use abstract roles in your sites and applications. They are for use by browsers. They are included for reference only.

outputs as

<div class="notecard note" id="sect1">
  <p><strong>Note:</strong> Don't use abstract roles in your sites and applications. They are for use by browsers. They are included for reference only.</p>
</div>
Pros
  • none
Cons
  • Muddies the sigil/symbol for blockquotes in Markdown by making the > pull twin duties in the parser. Other Markdown parsers, such as CommonMark, will turn this into a unsemantic <blockquote> which is bad.
  • Rendering the container as a <div> gives this element no semantics.
  • <strong> indicates that the label/title is important, which it is, but feels rather ad-hoc.
  • Does not get any special rendering for TUI browsers & reading mode

Docusaurus’s Markdown fork for admonitions

Our input

:::note
Some **content** with _Markdown_ `syntax`. Check [this `api`](#).
:::

outputs as

<div class="theme-admonition theme-admonition-note admonition_o5H7 alert alert--secondary">
	<div class="admonitionHeading_FzoX">
		<span class="admonitionIcon_rXq6">
			<svg viewBox="0 0 14 16">
				<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
			</svg>
		</span>
		note
	</div>
	<div class="admonitionContent_Knsx">
		<p>Some <strong>content</strong> with <em>Markdown</em> <code>syntax</code>. Check <a href="#">this <code>api</code></a>.</p>
	</div>
</div>
Pros
  • ::: syntax is the CommonMark proposal for generic directives at least it’s not breaking spec unlike using the > assigned to blockquotes
Cons
  • Rendering as a <div> gives this element no semantics.
  • Does not get any special rendering for TUI browsers & reading mode

Some W3C documents

Outputs

<p class="note">
	The <code id="the-blockquote-element:htmlquoteelement"><a href="#htmlquoteelement">HTMLQuoteElement</a></code>
	interface is also used by the
	<code id="the-blockquote-element:the-q-element"><a href="text-level-semantics.html#the-q-element">q</a></code>
	element.
</p>
Pros
  • none
Cons
  • The label we can see as “Note” is using a CSS ::before pseudoelement which means many contexts—including web scrapers—will have no idea it exists.
  • Does not get any special rendering for TUI browsers & reading mode, but even worse than the previous section because without a label, this just looks like a regular paragraph.

Other (better) W3C documents

Outputs

<div class="note" role="note" id="issue-container-generatedID-16">
	<div role="heading" class="note-title marker" id="h-note-14" aria-level="4">
		<span>Note</span>
	</div>
	<p class="">
		Because <code>document</code> and <code>application</code> elements
		can be nested in the <abbr title="Document Object Model">DOM</abbr>,
		they can have multiple <code>contentinfo</code> elements as
		<abbr title="Document Object Model">DOM</abbr> descendants, assuming
		each of those is associated with different document nodes, either by
		a <abbr title="Document Object Model">DOM</abbr> nesting (e.g.,
		<a href="#document" class="role-reference"><code>document</code></a>
		within
		<a href="#document" class="role-reference"><code>document</code></a>)
		or by use of the
		<a href="#aria-owns" class="property-reference"><code>aria-owns</code></a>
		attribute.
	</p>
</div>
Pros
  • The note role role is a nice fit because “represents additional information or parenthetical context to the primary content it supplements”
Cons
  • Rendering the container as a <div> gives this element no semantics.
  • Does not get any special rendering for TUI browsers & reading mode

What did the roundup teach us?

While there are more options, most of them fall into similar camps. <table> & <aside> elements try to use some non-generic (e.g. <div>) option. The <table> satisfies a lot of visual ideas while sort of missing semantics. <aside> goes for a semantic element, but whose implied role only makes sense for “tip” callouts (in fact, HTML5s output extension for Asciidoctor only has select callout kinds turning into <aside> elements for this reason). The <blockquote> seems to be a common element used in Markdown forks; I would speculate because this often has the result of ‘putting a box around something’ with the styling as many don’t include quotation marks that would make it more obvious that this has the wrong element semantically (tho I hate to admit, that breaking semantics aside, this element choice does give current TUI browsers & reader mode a called-out look like <table>). <div> always ends up too generic for our reader mode & TUI browsers to understand what to do with it. role=note was the best option to handle semantics. It’s always best to show a text label (you could maybe hide it behind a title attribute for some viewing modes tho if the imagery really is strong & clear).

note role

A section whose content represents additional information or parenthetical context to the primary content it supplements.

A note is content provided by the author of the page or document[…]

When used within the normal flow of a page’s content, a note has an implicit association with the content that it supplements.

—Accessible Rich Internet Applications (WAI-ARIA) 1.3, <https://w3c.github.io/aria/#note>

The most standout marks of trying to ‘solve’ the issue of “what does a semantic callout look like?” comes in form Docutils+reStructuredText’s <aside> & W3C’s role=note. Docutils wants to get us a semantic markup element despite more often than not being semantically incorrect, while W3C’s ARIA role gets us the semantic meaning, but with a generic container element. What’s missing is a semantic element with the correct implied role—and reading thru the markup specifications, I don’t see any elements with the implied role of note.

Proposing a new semantic standard

Inspired by a discussion on Asciidoctor’s issue tracker, what I would propose is a new element for callouts.

Name: callout vs. admonition (et al.)

Before pitching further, let’s clear up the naming situation. The two most common names I’ve seen for these types of elements is “callouts” & “admonitions”. After a discussion with a coworker & soon after seeing more folks bewildered, it becomes obvious that “admonition” isn’t a word in most folks’ wordlist. “Admonition” is another word coming from the French tongue that, while sounds showy/smart, isn’t immediately clear to the commonfolk. Compare that to “callout” which is a mix of “call” + “out”, both of which are simple words, coming from Proto-Germanic & being some of first English-as-a-second-language vocabulary. The name “callout” has, annecodetally, been clearer to folks just learning about the concept (or rather that the concept has a name) & tracks with other discussions I’ve watched online. This also mirrors the simple naming for “pull quote” (or “pull-quote”, or “pullquote”) which has a similar typographically usage highlighting a piece of content.

The <callout> element

Categories:
  • Flow content
  • Palpable content
Contexts in which this element can be used:
  • Where flow content is expected
Content model:
  • One cot element followed by flow content
Content attributes:
  • kind — specifies the kind of callout (such as “tip”, “note”, “danger”, “attention”, but can be any text)
Tag omission in text/html:
  • Neither tag is omissible
Implicit ARIA role:
  • note

The callout element represents a section to draw special attention to or present additional information in context to the primary document.

The kind attribute, if present, indicates the kind of callout this is. If the attribute is absent, a generic kind should be used by the user agent. A user agent may match & style a callout by default based on it’s kind such as a ::marker symbol for the callout, and/or choosing a default color scheme (tho defaults should exist for omitted or unmatched kinds).

The first cot element child of the element, if any, represents the title or label of the callout. If the cot is omitted, and the user agent has a matching, localized title for the parent callout element’s kind attribute, a default should be displayed (e.g. with <callout kind="warning"> and the user agent locale is th, a <cot>คำเตือน</cot> may be implied).

All elements after the first cot are considered a part of the body content of the callout.

The <cot> element

Contexts in which this element can be used:
  • As the first child of a callout element
Content model:
  • One cot element followed by flow content
Tag omission in text/html:
  • Neither tag is omissible

The cot element represents the callout title, a title or label for the rest of the cot element’s parent callout element.

For ARIA purposes, the cot might also be the labeledby element.

Users should set the cot element’s text content in relation to the parent callout element’s kind attribute, such as a translation.

Example

Input HTML

<callout kind="tip">
	<cot>Tip</cot>
	<p><em>This</em> is an example of a callout of <strong>tip<strong> kind.</p>
</callout>

renders as

Tip

This is an example of a callout of tip kind.

Inspiration: <details> + <summary>

The <details> & <summary> elements have a similar vibe. There is a container element + a first child for the label/title/legend with the rest of content being the body.

Limitations

Until user agents pick up the <callout> + <cot> elements, the elements will not have any default styling that will ‘call out’ to the readers, meaning TUI browsers & reader mode do not get a special visual. However, the implied role="note" should help other readers. Unsupported elements usually default to <div> behavior which means we should at least see a newline for our <callout> & again at our <cot>.

…Lorem ipsum…

Tip
This is an example of a callout of tip kind.

This is still fairly clear even without styling or first-class support.

Using <callout> today

Luckily in HTML5, we are allowed to create our own arbitrary elements & attributes to achieve desired results. With sprinkling of CSS we can get it to behave as expected as well. To do this we will need to clearly add our role="note" & the default display.

<callout role="note" kind="tip">
	<cot>Tip</cot>
	<p><em>This</em> is an example of a callout of <strong>tip</strong> kind.</p>
</callout>
callout, cot {
	display: block;
}

If you prefer adding CSS class naming scheme, I might suggest something like

<callout role="note" kind="tip" class="Callout Callout--tip">
	<cot class="Callout-title">Tip</cot>
	<p><em>This</em> is an example of a callout of <strong>tip</strong> kind.</p>
</callout>
Beware

As pointed out, while HTML5 parsers and generalistic XML parser won’t have a problem using this markup ‘today’, some parsers not specific to HTML5 may fail.

It’s also possible that some day <callout> or <cot> could either be adopted/changed in a similar-yet-different manner making the semantics possibly unexpected in the future. role="note" should help mitigate this to a degree tho.

Call to action (again)

I’m just a software maker. While I’ve ‘been doing’ front-end nearing 1½ decades, I have no experience writing RFCs. If you do have that experience & would like to make this a reality, my contact details prioritized in the footer of this weblog post.