Markdown "Callouts": styling the <details> element

Continuing the discussion from Markdown Admonitions and Markdown Template / style like “read the docs” for documents:

Markdown “Callouts”: styling the <details> element

Introduction

Many here will be familiar with the popular note-taking app Obsidian. I know many use it together with DEVONthink by indexing their vault(s). For those unfamiliar, Obsidian uses markdown for its basic formatting, but offers a ton of features, plugins and customizations on top of this. One popular feature is know as Callouts. (GitHub’s markdown has something similar called Alerts.)

Callouts are sections of text styled to stand out from the normal text flow. A colored box with a colored title, and an icon to signify the type of information within. In a way they look like heavily styled blockquotes. Oh, and they can be collapsed.

I haven’t used Obsidian’s Callouts beyond testing the feature briefly, since it uses a syntax unique to Obsidian… And I mainly use Obsidian as an external markdown editor when the mood strikes.

I haven’t felt I was missing much—but I do see how it can be useful to highlight sections or include secondary information that can be easily hidden or shown.

Well, I was in a mood for tinkering. In an earlier discussion @chrillek suggested that aside would be the proper HTML element to use. I have now been tinkering with styling the details element into something resembling Obsidian-style Callouts and thought someone here might appreciate it. I originally intended to post in the linked discussion, but but this grew long enough that it made sense to start a new one.

Hopefully this can be helpful for those newer to CSS, or inspiration for the more seasoned.

I’m happy enough with my results so far that I think I will experiment with using these in my notes in DEVONthink. There might be problems showing them in other apps, but that is of less concern to me than the reverse, since DEVONthink has been the center of my notes for a long time. At least this is HTML and CSS, not novel markup syntax that can only be used in one app.

Here are two screenshots (light and dark) and a GIF to show the dynamic elements:

Callout GIF (light)


My goal:

  • A visually distinct element that still doesn’t overpower the normal text flow.
  • An easy way to make variants of this element that are distinct yet follow a similar style.

The <details> element

Adding HTML-tags is not “pure” (Multi)Markdown, but since MMD is rendered as HTML for output, we can mix in HTML-tags just fine.

The strength of the details element compared to a generic div or aside is that it is dynamic: it can expand and collapse, just like Obsidian’s Callouts. And like the Callouts, you can set a default state of open or closed.

Using it in a markdown document looks like this:

<details>
<summary>Title or Callout</summary>

Your text. All normal markdown formatting
still works here. Lists, headers, images,
code blocks and so on...

</details>

To make it easy to use, create a snippet in your text expander of choice.

It is important that summary is on new line after details, and that you leave an empty line between your main text and enclosing HTML tags. Otherwise the markdown parser chokes and/or starts adding empty <p> tags, which will likely mess up your styling.

To set the default state as open, simply add open="true" in the opening details tag. (Note, to reverse this you must remove the open attribute completely, you can’t just set it to false).

For quick reference, here is the MDN web docs page on <details>.

Styling

Notes:

  • This is not supposed to be a finished plug ’n play stylesheet. I will share my approach as a starting point for further customization.
  • While I no longer consider myself a complete CSS beginner, I still have much to learn.
  • This is my first time writing CSS using nested rules and selectors, so I don’t know how pretty it is to more experienced programmers. But I do find that it makes it easier to read more complex rules.

With that out of the way…

WebKit’s (and DEVONthink’s) default styling of the details element is extremely barebones. So much that it can be easy to overlook. It’s just normal body text with an arrowhead (“disclosure triangle”/“disclosure widget icon”) to indicate that it can expand. It makes sense semantically—this is for additional details. But it is too minimal for my taste, and in particular for the task at hand.

Since I don’t like the default, I will change the default and use that as a baseline for variants. If you want to keep the default, you can create a generic .callout class as a baseline for variants.

Approach

  • Add borders and background
  • Color text and elements
  • Adjust margins and padding
  • Since the element is dynamic, I want a bit of dynamic styling too for indicating state/interaction
    • closed: Highlight border on hover (and keep highlight when open so it’s not too jittery)
    • open: Fade title/summary, but highlight it on hover
  • Use CSS variables for easy variations
    • For variants, hide the disclosure triangle and use a custom symbol instead

Selectors

Of course we have the details and summary elements. Then we have a :hover pseudo-class and an ‌[open] attribute. And a pesky ‌::-webkit-details-marker for the disclosure triangle. My basic structure looks like this:

details {

    attribute: value;
    attribute: value;
    ...

    > summary {

        &::before {
/* 		For custom callout symbols */
        }

    }

    &:hover {

    }

    &[open] {

        &:hover {
/* 		This selector targets the whole open <details>
		when hovering. I don’t use it, but show it here
		for demonstration. */
        }

        > summary {

            &:hover {
/* 			When <details> is open, I want only the <summary>
			to change on hover, so I use this selector. */
            }

        }

    }

    ::-webkit-details-marker {
/* 	For adjusting or hiding the disclosure triangle */

/* 	Note: If you want to use this in browers not using WebKit,
	you need to find an equivalent selector.
	According to MDN docs: “The disclosure triangle itself can
	be customized, although this is not as broadly supported.”
	The ::-webkit-details-marker is not listed in their reference,
	I found it by looking with Safari’s developer tools. */
    }

}

I want the text to be slightly smaller than my default body text. When I do that, I also like to decrease the line height and paragraph spacing a bit. I’m also gonna adjust the spacing of list items accordingly. So I add these selectors:

details {
	font-size: .9em;
	
	:is(p,ol,ul) {
		line-height: value;
	}
	> p + p,
	> blockquote p + p {
		margin-top: value;
	}
	:is(ol,ul) {
		margin-block-start: value;
		margin-block-end: value;
		
		:is(ol,ul) {
/*		Since nested list items are wrapped in <ol>/<ul> */
			margin-block-start: value;
			margin-block-end: value;
		}
	}

}

I also want to adjust some margins and padding:

details {

	> :not(summary) {
/*	Whitespace on inside between border and text.
	Haven’t figured out how to make relative units
	work for this, so I use px. */
		margin-left: 20px;
		margin-right: 20px;
	}

/* 	Normalize padding and margin of first and last element: */

	> :nth-child(2) {
/* 	  Selects the first element after <summary> */
		padding-top: 0;
		margin-top: .2em;
	}
	> :last-child {
/*	  Last element in box */
		padding-bottom: 0;
		margin-bottom: 15px;
/* 		px again since I don’t want it to change
		depending on the element */
/* 		I started with uniform margins, but like
		slightly wider vertical margins. */
	}

}

Variables

Let’s make up some variables. A bit verbose, but readable by humans—also if your future self hasn’t looked at it in a while…

--callout-bg-body: ;
--callout-border-closed: ;
--callout-border-hover: ;
--callout-border-open: ;
--callout-title-closed: ;
--callout-title-closed-hover: ;
--callout-title-open: ;
--callout-title-open-hover: ;

/* Some variables if you want separate
background colors for the <summary> : */
--callout-bg-title-closed: ;
--callout-bg-title-closed-hover: ;
--callout-bg-title-open: ;
--callout-bg-title-open-hover: ;

/* For the disclosure triangle and custom symbols: */
--disclosure-triangle-display: ; /*
  default is inline-block, hide with "none" */
--disclosure-triangle-margin: ;
--callout-symbol: ;

Now we can create custom classes and simply fill out the variables. As value I set… mainly other variables defined earlier in my stylesheet.

.notice {
	--callout-bg-body: var(--color-bg-secondary);
	--callout-border-closed: value;
	--callout-border-hover: value;
	...
	--disclosure-triangle-display: none;
	--callout-symbol: "⚠️ ";
}
.info {
	--callout-bg-body: var(--color-bg-secondary);
	--callout-border-closed: value;
	--callout-border-hover: value;
	...
	--disclosure-triangle-display: none;
	--callout-symbol: "ℹ ";
}

Combining it all

To flesh out the skeleton, here is a snapshot from my stylesheet.

/*
-----------------------
CALLOUT-style <details>
-----------------------
*/
details {
/*  Variables: */
    --border-width: .12em;
    --callout-bg-body: var(--color-bg-secondary);
    --callout-border-closed: var(--color-ui-normal);
    --callout-border-hover: var(--color-ui-active);
    --callout-border-open: var(--color-ui-active);
    --callout-title-closed: var(--color-tx-muted);
    --callout-title-open: var(--color-tx-faint);
    --callout-title-hover: var(--color-tx-muted);
    --disclosure-triangle-display: inline-block;
    --disclosure-triangle-margin: .4em;
    --callout-symbol:;

    /* Not used:
    --callout-bg-title-closed: ;
	--callout-bg-title-open: ;
	--callout-bg-title-open-hover: ;
	*/
}
details {
    margin-block-start: 1.2em;
    margin-block-end: 1.2em;

    background: var(--callout-bg-body);
    overflow: hidden; /* 
    So background doesn’t clip into border */

/* Basic Text Adjustments: */

    font-size: .9em;
    :is(p,ol,ul) {
        line-height: 1.45em; /* 
        (My default line-height is 150%, for comparison) */
	}
    > p + p,
    > blockquote p + p {
        margin-top: .75em;
    }
	:is(ol,ul) {
		margin-block-start: .4em;
		margin-block-end: .4em;

		:is(ol,ul) {
/* 		Nested list elements are wrapped in <ol>/<ul>
		I want almost no extra spacing inside a list
		on top of what I already set for li + li */
			margin-block-start: .1em;
			margin-block-end: 0;
		}
	}

/* Inner Margins & Padding: */

    > :not(summary) {
        margin-left: 20px;
        margin-right: 20px;
    }
    > :nth-child(2) {
        padding-top: 0;
        margin-top: .2em;

			&:is(h2,h3) {
/* 			For headers I like a bit more empty space
			after <summary>. (h1 already has enough) */
		        margin-top: .25em;
	        }
    }
    > :not(figure):last-child {
        padding-bottom: 0;
        margin-bottom: 15px;
/* 		I started with uniform margins, but like
		a little more space on the vertical sides. */
    }

/* Borders & Colors, Interaction: */

    border-style: solid;
    border-width: var(--border-width);
    border-radius: .5em;
    border-color: var(--callout-border-closed);
    &:hover {
        border-color: var(--callout-border-hover);
    }

    > summary {
        color: var(--callout-title-closed);
	/*  background: var(--callout-bg-title-closed); */
        font-size: 1.05em;
        font-weight: 600;
        padding-top: .15em;
        padding-bottom: .2em;

        &::before {
            content: var(--callout-symbol);
            margin-inline-start: var(--disclosure-triangle-margin);
        }
    }

    &[open] {
        border-color: var(--callout-border-hover);

        > summary {
            color: var(--callout-title-open);
	    /*  background: var(--callout-bg-title-open); */
        	&:hover {
    	        color: var(--callout-title-hover);
		    /*  background: var(--callout-bg-title-open-hover); */
	        }
        }

    }

/* Disclosure Triangle / Widget: */

    ::-webkit-details-marker {
/*		Offset disclosure triangle/callout symbol from border: */
        margin-inline-start: var(--disclosure-triangle-margin);
        display: var(--disclosure-triangle-display);
/* 		By default the marker is not vertically aligned with the text.
		I adjusted the postion like this: */
        position: relative;
        bottom: .06em;
    }

}
/* Example Callout Class: Notice */
.notice {
	--callout-border-closed: var(--color-ui-normal);
	--callout-border-open: var(--color-ye-hover);
	--callout-border-hover: var(--color-ye-hover);
	--callout-title-closed: var(--color-ye);
	--callout-title-open: var(--color-ye-hover);
	--callout-title-hover: var(--color-ye);
	--callout-symbol: "⚠️ ";
	--disclosure-triangle-display: none;
}

Color scheme

If you want to know, I use the color scheme Flexoki created by Steph Ango (incidentally one of the creators of Obsidian) with a few tweaks. He has a web page to showcase it here, and there is also a GitHub repository including theme files for many text editors and some other apps. (I also use it in CotEditor and Terminal.)

For many years I swore by Solarized, but the contrast is a bit too low. There are some modernized versions of it adjusted for better contrast (namely OKSolar and Selenized). But I quite like Flexoki for a “normal” look and have been settled on that for some time.

If you like the default look of Callouts in Obsidian, it shouldn’t be too hard to find the color values and use them in your style sheet.

Fonts used: Source Serif 4 for serifs, IBM Plex Sans for sans-serif, and iA Writer Mono for monospaced.

2 Likes

Personally, I doubt that details/summary is the right approach for admonitions: If you want to warn people, you should not require them to click on a button to see the warning. And I suppose there are accessibility issues with this approach. These issues also exist for the info and notice callouts because it’s not obvious at all that the little icons can (and must) be clicked to see the details.

Check out Accessible description - MDN Web Docs Glossary: Definitions of Web-related terms | MDN in the context of the summary element that is meant to provide the “accessible description” for the details

In any case, I suggest using <details open="open"> in the MD source to make sure that the admonition is initially visible.

Here’s a quite thorough discussion of different approaches for admonitions:

1 Like

I appreciate your input!

Of course it all depends on the purpose and use case. As I wrote, I have mostly overlooked callouts, so I might be ignorant of several things.

But the shown example is for personal notes, with myself as the only intended audience. I thought that was clear, but maybe I should change the heading “The Goal” to “My Goal” for more clarity.

I was mainly interested in the ability to embed information that is relevant but of secondary importance, and to distinguish categories should I want to. That is also why I like how they collapse. I guess “details” is in fact a better description here than “callout/admonition”…

My choices and styling follow from that, and I feel I archieved what I had in mind quite well for a first pass. I specifically didn’t want them to grab too much attention and disrupt the reading flow—so I intentionally went for a subdued style.

I doubt any would copy the look. But it was a fun exercise, and I thought the backbone might be useful to others. While a web developer probably doesn’t need help styling a details element, others might learn something.

The point was to share the CSS skeleton, and I didn’t want to make the post even longer, so I didn’t elaborate much on my aesthetic choices. To me it is quite clear that they are interactive… but of course it is: I designed them! (The shape and color communicates to me that they are distinct elements, and the border highlighting on hover communicates that they can be clicked. And you can click the whole summary, just not the little symbol).

I agree my example doesn’t work for “traditional” Callouts, how they are described in your link:

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.

– thanks for sharing btw., I will give it a proper read later.

But it is quite easy to make a details element look more like this (and set the default to open="open" as you suggest):

I experimented with something like that, but I felt it stood out too much for what I wanted. Maybe I should add an example like that anyways.

I am no programmer or web developer, so I defer to you on the accessibility issue. I have little awareness of navigating UI with assistive technology. I will however note that the summary text was just placeholder. I imagined one would add a fitting description depending on the content.

Likewise for the symbols. It was just vaguely similar to examples I have seen. And I actively chose to hide the disclosure triangle, because color + symbol was already enough for me. I felt the triangle was too visually noisy. But you can just keep it.

1 Like

It’s surely fun stuff to mess about with and if it scratches your itch (or someone else’s), it’s worth a share IMHO. I’ve flexed many things in Markdown and CSS that purists would thumb their nose at, but that’s okay too. :slight_smile:

2 Likes

My remarks were not intended as criticism of your work but rather as a look at it from another perspective. Since I worked as an editor for a long time, I’m still mostly thinking about publishing in the context of Markdown. Which is quite different from the “I’m just using it for myself” perspective.

And I didn’t say anything about the aesthetic aspect, as I’m not qualified at all to talk about that. And views about aesthetics can vary wildly :wink:

My main issues are semantics, usability and accessibility, with which I tend to be a purist (@bluefrog). All these aspects are irrelevant, of course, if you are writing only for your own eyes.

2 Likes

@chrillek I did not take it as criticism either :smiley: But since I put in a little effort in writing this up, I felt I should respond to make sure I actually communicated what I had intended.

What you say makes all the more sense with your background in mind. And it is good to point out for anyone who should consider using this in writing aimed at an audience greater than 1.

From the link you shared, it seems there is no optimal HTML solution at the present moment. I did chuckle when I reached this:

Obsidian’s proprietary Markdown fork for Callouts
Pros:

  • none

I also see that the author actually lists <details> + <summary> as inspiration for the proposed new semantic elements <callout> + <cot>.


@korm the definition list is indeed handy. But I think it is too far from what you want if you have something like a callout/admonition in mind. You would at least still need to style it with CSS, then.

If you want a quick and easy approximation that is closer (at least visually, if not semantically), I would just use a simple blockquote. Though if you use a lot of those and want something different, a definition list is one tool close at hand.