Building Linkable Tabs with only CSS

Following Jesus; Husband; Father; Developer; Gamer; Tinkerer; Writing about code
Search for a command to run...

Following Jesus; Husband; Father; Developer; Gamer; Tinkerer; Writing about code
No comments yet. Be the first to comment.
Today ChatGPT lied to me, so I’m setting the record straight. This is my question: “If an endpoint in an OpenAPI spec has multiple tags, what will Swagger Codegen do?”. Because Swagger Codegen organized the generated code into api/tag_name_api files....

A Short Story

Universal Libraries, The Internet, and Generative AI

This is the article I wished existed when I needed to support accounting functions. Double-entry Bookkeeping may seem esoteric and unnecessarily complicated, but actually describes a simple framework for maintaining a correctible and auditable record...

How to Change Databases without Downtime

The venerable Tab. Some of us have too many on our browsers... Before using JavaScript to build more, consider what just HTML and CSS can do.
There's actually many ways to build tabs with just HTML and CSS (we'll explore an alternative in another post; follow me to be notified when that comes out). In this article, we're going to use the :target selector.
:target selector to show the current tab content while hiding the others a. :target - selects the element with the unique id corresponding to the URI's fragment
b. :last-child - selects the element that is the last child of its parent
General sibling combinator ~ - combines that selector on the right-hand side to limit to only elements that are also subsequent siblings of elements selected by the left-hand side
Class selector . - selects all elements with the given class name
display
a. block or anything besides none to render the tab content
b. none - removes the element from having any effect on the rendered page
color / border-color / your-fancy-tab-styling-here -whatever styles should change on the tabs to indicate being active/inactive for the particular design.
Our tabs will be sections with a class of tab (for selecting) as siblings of one another. These tabs depend on the URI fragment; if you intend to show a default tab if there is no fragment (I do), it is important to order the tabs in the HTML so that the default tab appears last (I want the first tab to be default open, so I place my tabs in reverse order).
Inside each tab, the first element will be our navigation. Unfortunately, to style the tab navigation requires duplicating the navigation in each tab. This could be avoided, but would require other compromises, which we will discuss later.
<section class="tab" id="last-tab">
<nav>
<a href="#first-tab">First Tab</a>
<a href="#last-tab" class="active">Last Tab</a>
</nav>
<p>Last tab content</p>
</section>
<section class="tab" id="first-tab">
<nav>
<a href="#first-tab" class="active">First Tab</a>
<a href="#last-tab">Last Tab</a>
</nav>
<p>First tab content</p>
</section>
Start off by hiding all the tabs:
.tab { display: none; }
Now show the one tab whose id matches the URI fragment using the :target selector:
.tab { display: none; }
.tab:target { display: block; }
Let's also go ahead and throw in the style to show which tab is "active":
.active {
color: teal;
border-bottom-color: teal;
}
.tab { display: none; }
.tab:target { display: block; }
And we now have a totally functioning tab system that is:
But, visiting the page without a current fragment will result in none of the tabs showing (until one is clicked)... let's fix that.
We placed our default tab as the last in the HTML, so let's use :last-child:
.tab:last-child { display: block; }
At this point, you are probably wondering why we didn't just order them normally and use :first-child?
We can't only display the default tab. We also need to hide the default tab if any other tab has been targeted.
So how do we do that?
With the general sibling combinator!
Take a moment to read the description of what ~ does. A key thing to understand is that it only selects subsequent siblings. That is: it only selects downward through the HTML; not upward to previous elements.
That is why we have to place our default tab last. We want to select and hide the default tab if it is a sibling to another tab that is currently targeted by the fragment. Since ~ only works downward, our default has to be last.
.tab:last-child { display: block; }
.tab:target ~ .tab:last-child { display: none; }
Put it all together (and throw in some additional layout) and we have a quite nice, linkable, tab system:
.active {
color: teal;
border-bottom-color: teal;
}
.tab { display: none; }
.tab:target { display: block; }
.tab:last-child { display: block; }
.tab:target ~ .tab:last-child { display: none; }
I chose to duplicate my navigation within each tab in order to allow styling of the active tab.
This may not be the right decision for you. There are other options:
+. Absolutely position the tab content into the reserved area.and I am sure there are more...
And, of course: use a little JavaScript. When needed it can be the right tool. This is just an experiment to make you think about when it actually is the "right" tool, or just a hammer beating your design into submission.
Thanks for reading! I like sharing and finding things like this where common web design patterns can be done without a massive JavaScript library or framework bogging the page down. Give me some suggestions below on patterns you'd like to see me break-down in CSS/HTML-only for this series.