GitHub LinkedIn E-Mail

Fancy Inline Asides

Picture of the author
By Travis Fletcher
on

I recently read an article on Thirty-Five about something unrelated and really liked one of the effects they used throughtout the article. They had these little colored and styled icons through the blog that, when you clicked on them, would expand on the content with more deteails in a separate pane — Either directly below or to the side of the text depending on your screen size. That's pretty cool! Let's try to reproduce it.

Ground rules

  1. The content should be accessible to a screen reader and keyboard navigable
  2. The component should use the minimum amount of JavaScript possible, and rely on cross-platform web standards when available.

Starting easy

MVP for the feature would be a simple

details MDN

element. It's web native, expands when you click on it, and even has a mechanism for ensuring only one panel is open at a time, which will be useful later.

Unfortuantely, it has a few major drawbacks. The first is immediately evident — details are block-level elements. A CSS class with display: inline can get us part of the way there, but we still have the issue of it not being phrasing content to the browser rendering engine, which means it will close any p tags, defeating the purpose of making it inline. For the sake of trying to push this idea through, I'm going to put a pin in that one for now in order to see how far I can get, but consider this your foreshado-warning.


Lets's try again. here is another
details
details {
  display: inline;
}
with some text after it. Looks better while closed, but still breaks the layout when open, since the extra height from the details element expands upwards. We can fix that by adding a
baseline-source: first
details {
  display: inline;
  baseline-source: first;
}
property to the details element, which will make it expand downwards. Now what do we do about the width? It's currently expanding outwards to the right and pushing the rest of the text of the line out of the way.

Well, if we dont want something in the flow of the document, we remove it. Good old
position: absolute
details {
  display: inline;
  baseline-source: first;

  &:open > *:not(summary) {
    position: absolute;
  }
}




(this space is intentionally left blank)





isn't just for attaching objects to screen space. It removes the element from ther document flow, allowing us to make space for it ourselves. By
using JS to set a margin-bottom
el.addEventListener("toggle", () => {
  const divEl = el.children[1];

  el.style.paddingBlockEnd = "0px";

  const targetHeight = window.getComputedStyle(divEl).height;
  if (el.open) {
    el.style.paddingBlockEnd = targetHeight;
  }
});
on open set to the height of the hidden contents, we can a pretty convincing recreation of the original effect. We also had to replace the default disclosure triangle with
our own
& > summary {
  cursor: pointer;

  /* Make the details look like our links */
  text-decoration: underline;
  text-decoration-thickness: 2px;
  color: var(--primary-link-color);
  text-decoration-color: var(--secondary-link-color);

  /*
    inline-block is necessary for chrome to not break
    the line after the discolsure, and also functions
    to turn off the default disclosure triangle so we
    can add our own
  */
  display: inline-block;
  position: relative;
  padding-inline-start: 2ch;
}

/*
  credit to the wonderfully inspiring folk over at
  https://css-tricks.com/how-to-animate-the-details-element/
*/
details.inline > summary::before {
  content: "";
  border-width: 0.3em 0 0.3em 0.6em;
  border-style: solid;
  border-color: transparent transparent transparent currentColor;
  position: absolute;
  top: 0.4em;
  left: 0.4ch;
  transform: rotate(0);
  transform-origin: 0.2rem 50%;
  transition: 0.25s transform ease;
}
details.inline[open] > summary:before {
  transform: rotate(90deg);
}
(and took the opportunity to animate it), due to chrome compatibility pushing me towards turning the summary into a display: inline-block; as they really did not like to show margin/padding on my inline summary element.

Dealing with the element in the room

Early browsers made some... questionable decisions that can lead to unexpected behavior when you do things they don't expect. In our case here, the browser attempts to helpfully close paragarph tags when it thinks you are done with them. Since it assumes that the details element is going to be a block element, it closes the currently open P tag when hit. This is not what we want, and I have been avoiding the isuse thus far by not using p tags when I knew I was going to include a details element inside of the block of text. That sucks. It means I have to manually add back the side-wide margins that I normally get for free by writing semantic HTML and it means that the inclusion of a details element 'leaks' out and affects how you write surrounding content. Moreover, a details element is not Phrasing Content and thus not technically allowed inside of a P tag even if we could force it.

I am still working on a solution to this :(