Javascript-Free Inline Hide Spoiler Tags

The basic related html is:

<details class="spoiler">This text is hidden</details>
Giving you this:
This text is hidden

This will also work with this html:

<details class="spoiler"><summary>Spoiler</summary>This text is hidden</details>
Giving you this:
SpoilerThis text is hidden

How I did it:

Step 1: Make the details tag flow inline with the content, and give it a background color.

I figured out this worked with inline-block rather than inline, and I also added a bit of padding because it ended up looking nicer.

Careful: By the HTML specs, you cannot put a <details> tag inside a <p> tag, regardless of it’s display: directive – doing so will automatically close the paragraph tag, and the spoiler will end up on it’s own line regardless. It will however still work. Putting this inside a div, table cell, or other container for your text works fine.

details.spoiler {
    display: inline-block;
    background: #999;
    padding: 0 0.4em;
}

Step 2: Hide the summary text, while having it continue to take up the same amount of space, and remain focusable/clickable

The styles used need to be applied to the details tag, because there’s no other way to style the auto-inserted summary. color:transparent makes the text invisible, while having it take up the same amount of space (unlike font-size: 0) and keeping it a focusable element with the on-click behaviour intact (unlike visibility: hidden).

details.spoiler {
    color: transparent;
}

Step 3: Bring the content up to the top line of the details box when expanded, rather than sitting below the (still hidden) summary text.

A negative margin-top does the trick; the exact value needs to scale with the line-height of the surrounding text. Alternately, you could specify a line-height on the details tag itself, and not worry about perfectly matching the surrounding text; at least that way it’s guaranteed not to overhang the grey box one direction or another.

We also need to reverse the color: transparent so that the content itself is visible, and a lighter shade of grey brings the text contrast into something appropriate. Keeping a background color set at all makes it more obvious that the user can still interact with it, and also will keep the contrast workable if it is being displayed on a dark background, since we can’t use inherit to get the surrounding text’s color (it would just inherit the details tag’s transparency).

The ::details-content pseudo-element will select all of the contents of the details box except for the summary (either specified or implied). (Too bad there’s no ::details-summary pseudo element to select the auto-inserted Details text directly, even though there is an element in the Shadow DOM, but we work with what we’ve got.)

Caution: Styling details-content while the details tag is closed has some weird effects, so specifying [open] makes sure we don’t do that. Using the :open state would presumably be marginally faster than checking for an attribute, but that’s not yet fully implemented cross-browser, and no human is going to notice the difference in page load speed.

details.spoiler[open] {
    background: #a0a0a0;
}
details.spoiler[open]::details-content {
    margin-top: -1.15em; /* this needs to match the line-height */
    color: black;
}

Step 4: Make it so that clicking on the spoiler again triggers the collapse, rather than being treated as a click in the content which doesn’t do anything.

pointer-events: none will make the contents ignore any click events and instead pass it through to whatever is directly below it as if it was not present. In this case, the transparent summary is still underneath, since we just moved the contents on top of it, and clicking on that will “collapse” the spoiler again, which is exactly what I want.

details.spoiler[open]::details-content {
    pointer-events: none; 
}

Step 5: If the <summary> tag is used, make it show, but only when the details tag is collapsed.

There’s no [closed] or :closed option to target, but :not() will do the trick nicely.

display: inline will also make the triangular list-marker disappear. For some reason you can’t put display:none on a ::marker pseudo-element (at least not in Chrome), but this works.

Adding a bit more padding here looks better in my opinion; adjust to taste.

details.spoiler:not([open]) summary {
    color: black;
    display: inline; 
    padding: 0 0.4em;
}

The complete code:

details.spoiler {
    display: inline-block;
    background: #999;
    padding: 0 0.4em;
    color: transparent;
    line-height: 1.15;
}
details.spoiler[open] {
    background: #a0a0a0;
}
details.spoiler:not([open]) summary {
    color: black;
    display: inline;
    padding: 0 0.4em;
}
details.spoiler[open]::details-content {
    margin-top: -1.15em;
    color: black;
    pointer-events: none;
}

Have a question?

Your email address will not be published. Required fields are marked *