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:
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.