|
| 1 | +--- |
| 2 | +title: Creating tabbed content without JavaScript |
| 3 | +shortTitle: Details tabbed content |
| 4 | +language: css |
| 5 | +tags: [interactivity] |
| 6 | +cover: flower-portrait-6 |
| 7 | +excerpt: Leverage modern HTML and CSS capabilities to create a tabbed content component without JavaScript. |
| 8 | +listed: true |
| 9 | +dateModified: 2025-05-26 |
| 10 | +--- |
| 11 | + |
| 12 | +A few weeks back, I was working on a **tabbed content component** and wondered if I could leverage the `<details>` HTML element to create a more semantic and accessible solution. After some experimentation, I found that it was indeed possible, but there's a small caveat: it needs the `::details-content` pseudo-element to work properly. |
| 13 | + |
| 14 | +<baseline-support featureId="details-content"> |
| 15 | +</baseline-support> |
| 16 | + |
| 17 | +As you may be aware by now, the `<details>` element creates collapsable sections of content. Adding the same `name` attribute to multiple `<details>` elements allows you to create an accordion-style component. We can build on top of that to create a tabbed content component. |
| 18 | + |
| 19 | +```html |
| 20 | +<div class="tabs-container"> |
| 21 | + <details name="tabs" open> |
| 22 | + <summary>First tab</summary> |
| 23 | + <div><p>First tab content</p></div> |
| 24 | + </details> |
| 25 | + <details name="tabs"> |
| 26 | + <summary>Second tab</summary> |
| 27 | + <div><p>Second tab content</p></div> |
| 28 | + </details> |
| 29 | +</div> |
| 30 | +``` |
| 31 | + |
| 32 | +@[Quick refresher](/html/s/details-accordion) |
| 33 | + |
| 34 | +In order for this trick to work, we'll have to wrap all of our elements in a `<div>` or similar **container** and make it a **grid**. We'll then set up our grid to be `N x 2`, where `N` is the **number of tabs** we want. |
| 35 | + |
| 36 | +```css |
| 37 | +.tabs-container { |
| 38 | + position: relative; |
| 39 | + display: grid; |
| 40 | + grid-template-rows: auto 1fr; |
| 41 | + /* For 2 tabs, adjust to your needs */ |
| 42 | + grid-template-columns: repeat(2, auto); |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +The **first row** will contain our tab _handles_ (the `<summary>` elements), and the **second row** will contain the content of each tab. We'll use the appropriate `grid-row` and `grid-column` properties to set these up correctly. |
| 47 | + |
| 48 | +```css |
| 49 | +.tabs-container > details > summary { |
| 50 | + grid-row: 1; |
| 51 | +} |
| 52 | + |
| 53 | +.tabs-container > details > div { |
| 54 | + grid-column: 1 / -1; |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +In order for the open tab to display correctly, we'll need to set the `<details>` element, along with the `::details-content` pseudo-element to `display: contents`. This essentially means that **the element will not generate a box**, but its children will be displayed as if they were direct children of the parent element. This allows us to keep the semantic structure of the HTML while still achieving the desired layout. |
| 59 | + |
| 60 | +```css |
| 61 | +.tabs-container > details { |
| 62 | + display: contents; |
| 63 | +} |
| 64 | + |
| 65 | +.tabs-container > details[open]::details-content { |
| 66 | + display: contents; |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +And that's pretty much all that's needed. Slap a few styles to make it pretty and you've got yourself a **JavaScript-free** tabbed content component. |
| 71 | + |
| 72 | +https://codepen.io/chalarangelo/pen/JoodqXo |
| 73 | + |
| 74 | +```html |
| 75 | +<div class="tabs-container"> |
| 76 | + <details name="tabs" open> |
| 77 | + <summary>First tab</summary> |
| 78 | + <div><p>First tab content</p></div> |
| 79 | + </details> |
| 80 | + <details name="tabs"> |
| 81 | + <summary>Second tab</summary> |
| 82 | + <div><p>Second tab content</p></div> |
| 83 | + </details> |
| 84 | +</div> |
| 85 | +``` |
| 86 | + |
| 87 | +```css |
| 88 | +.tabs-container { |
| 89 | + position: relative; |
| 90 | + display: grid; |
| 91 | + grid-template-rows: auto 1fr; |
| 92 | + grid-template-columns: repeat(2, auto); |
| 93 | +} |
| 94 | + |
| 95 | +.tabs-container > details { |
| 96 | + display: contents; |
| 97 | +} |
| 98 | + |
| 99 | +.tabs-container > details > summary { |
| 100 | + display: flex; |
| 101 | + justify-content: center; |
| 102 | + grid-row: 1; |
| 103 | +} |
| 104 | + |
| 105 | +/* Hides the ::marker pseudo-element */ |
| 106 | +.tabs-container > details > summary::marker { |
| 107 | + display: none; |
| 108 | + content: ""; |
| 109 | +} |
| 110 | + |
| 111 | +.tabs-container > details > div { |
| 112 | + grid-column: 1 / -1; |
| 113 | +} |
| 114 | + |
| 115 | +.tabs-container > details[open]::details-content { |
| 116 | + display: contents; |
| 117 | +} |
| 118 | +``` |
0 commit comments