Collapse in 5 minutes without JavaScript!
Building a fully working Collapse component in under 5 minutes using just HTML and CSS š„³
This is the text version of the video: NO JAVASCRIPT - Collapse with HTML and CSS in 5min! that I published on my YouTube channel (in Portuguese).
Itās worth watching! š
Introduction
In 2014 I created a project called CSS Components whose goal was to recreate Bootstrap components using just HTML and CSS, with no JavaScript at all.
Back then I imagined that in the future it would be really easy to build a bunch of native components using only CSS and HTML, but thatās not exactly what happened in the following years.
Quite the opposite, our dependence on JavaScript only grew (in Portuguese).
Until recently, to build these components without JavaScript, most of the time
we had to use techniques that mainly took advantage of the checked and
unchecked state of elements like checkbox and radio, and thatās exactly the
approach I used for most of the components in CSS Components.
The project is available on GitHub if you want to study the code š
That worked well in many cases, but it didnāt make sense for most projects.
But even though HTML and CSS donāt evolve as fast as JavaScript usually does, weāve had some really nice advances in this area.
Some of these new things are tags like <details> and <summary>, which let
you build a native collapse component using just HTML, and thatās what Iām going
to explain here.
The badass <details> and <summary>
All we need is two tags, <details> and <summary>, and we could even stop
right here, since the open and close effect is already fully working.
<details class="collapse">
<summary class="title">Titulo</summary>
<div class="description">Texto</div>
</details>But I also added a bit of CSS to make the example nicer. I started by adding
some CSS Variables, which
are the same ones I used in the article where I explained how they work,
and I also took the chance to add a few styles to the body.
:root {
--space-xxs: 4px;
--space-xs: 8px;
--space-sm: 16px;
--space: 24px;
--space-md: 32px;
--space-lg: 48px;
--space-xlg: 64px;
--screen-sm: 768px;
--gray: #555;
--gray-dark: #333;
--gray-darker: #111;
--gray-light: #f1f1f1;
--gray-lighter: #fafafa;
--blue: #187888;
--yellow: #e6af05;
--white: #fff;
--black: #000;
--brand-primary: var(--blue);
--background: var(--white);
--text-color: var(--gray-darker);
}
body {
background-color: var(--background);
color: var(--text-color);
font-family: 'Open Sans', sans-serif;
font-size: 16px;
}Then Iāll remove the default arrows, which work fine but are really ugly, and start styling this component.
details {
summary {
list-style: none;
}
}As you can see, Iām using SCSS as the CSS preprocessor, and Iāll start by
editing the <details> element that uses the .collapse class, adding
properties to center it and set a max width.
I also added border and shadow styles to make the element look nicer, and with
the :first-child selector I can make the margin apply only when itās the
first element, pulling it away from the top.
I also added margin-bottom only when itās not the last element, which keeps
the margin from leaking onto other elements.
A few years ago I wrote an article explaining the not() pseudo-selector (in Portuguese).
I also added some transition effects on the background-color of the
<details> element and used will-change, a property that helps optimize the
performance of certain properties during the CSS transition.
Finally, I changed the background-color property when the element is in the
:hover state, and with that we get a pretty decent hover effect.
.collapse {
border: solid 1px var(--gray-lighter);
border-radius: 4px;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.25);
margin-left: auto;
margin-right: auto;
max-width: 500px;
transition: background-color 0.25s;
will-change: background-color;
&:first-child {
margin-top: var(--space);
}
&:not(:last-child) {
margin-bottom: var(--space-sm);
}
&:hover {
background-color: var(--gray-lighter);
}
}The next step is styling the <summary> element, which I targeted with the
.title class, adding padding to improve the inner spacing and
cursor: pointer, which doesnāt come by default on this element.
.title {
cursor: pointer;
font-weight: 600;
padding: var(--space-sm);
position: relative;And last, letās style the .description class, which will only be visible
after clicking the <summary> element. Iāll also add a border, tweak the font,
and set the padding to match the one used on .title.
.description {
border-top: var(--gray-light) solid 1px;
font-size: 14px;
line-height: 21px;
padding: var(--space-sm);
}Now we already have a component thatās much nicer to look at and fully working. The video could end here again, but we can still make a few things even cooler, like changing the behavior when the element is open or closed and adding some transitions.
So, inside the <summary> element that uses the .title class, Iāll use the
CSS :after pseudo-element with absolute positioning, and instead of an arrow
icon Iāll use a cute and always useful emoji to help me in this example š
Since the :after pseudo-element is absolute, to center it Iāll use a
well-known technique with transform: translateX(-50%) and top: 50%, and
finally Iāll also position it on the right with right: var(--space-sm).
.title {
&:after {
content: 'š“';
position: absolute;
right: var(--space-sm);
transform: translatey(-50%);
top: 50%;
}
}But this can get even cooler. This time Iāll change our emoji inside the
:after on the .title class whenever the <details> element is in the hover
state.
And last, Iāll use the [open] selector, which is natively tied to the
<details> element, to switch the emoji depending on whether the element is
open or closed. Think about how many other possibilities weād have to play
around with from here.
.collapse {
.title:after {
content: "š";
}
}
&[open] {
.title:after {
content: "š";
}
}
}You can play around with this a lot and do some really cool stuff using CSS to make these state transitions even more badass.
Accessibility
And since <details> and <summary> are native HTML tags, theyāre
automatically accessible and semantic. You get that for free without having to
do any extra implementation.
Markdown
And one extra tip:
On top of everything, it works in markdown, which means you can easily add this feature to make your projectsā READMEs even cooler.
Result
Component finished, and as you can see I didnāt have to add a single line of JavaScript š
On Can I Use we can see that this feature is completely stable today and works in all browsers.
Awesome, right? š
Conclusion
Beyond semantics, HTML seems to be moving toward bundling more and more native features, which tends to save us a lot of lines of code and a lot of development time.
Weāre thankful for that.