79392606

Date: 2025-01-28 02:11:48
Score: 0.5
Natty:
Report link

The HTML5 <details> element semantically means "more content here". With its show-hide function, reflected attribute, and coordinating name attribute, it's almost a perfect fit for implementing tabs without JavaScript. Almost.

perfect semantics, poor layout

<div class='tabs'>

    <details class='tab' name='these_tabs' open>
        <summary class='tab-title'>Tab One</summary>
        <p>Lorem...</p>
        <p>Ipsum...</p>
    </details>

    <details class='tab' name='these_tabs'>
        <summary class='tab-title'>Tab One</summary>
        <p>Lorem...</p>
        <p>Ipsum...</p>
    </details>

</div>

This is semantic. Unfortunately, even with subgrid now widely adopted, this limits the tab and its content to using the same width. Either the sibling tabs are pushed off to the side so the active tab's content has enough room, or the active tab's content is crammed into as narrow a space as possible.

So could the content be moved outside the details element?

partly semantic, good layout

<div class='tabs'>

    <details class='tab' name='these_tabs' open>
        <summary class='title'>Tab one</summary>
    </details>
    <div class='content'>
        <p>Lorem...</p>
        <p>Ipsum...</p>
    </div>

    <details class='tab' name='these_tabs' open>
        <summary class='title'>Tab one</summary>
    </details>
    <div class='content'>
        <p>Lorem...</p>
        <p>Ipsum...</p>
    </div>

</div>
.tabs {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: auto auto;
}
.tab {
    grid-row: 1;
    max-width: max-content;
}
.tab-content {
    grid-column: 1 / 99; /* [1] */
    grid-row: 2;
}
.tab[open] .tab-title {
    pointer-events: none; /* [2] */
}
.tab:not([open]) + .tab-content { /* [3] */
    display: none;
}

This is less semantic, because the content to-be-revealed is not actually inside the <details> element, so a screen reader would have to infer its connection from proximity and visibility. On the other hand, it does use the appropriate element for interaction. It also achieves flexible horizontal layout of the tabs where the active tab can be selected and styled. And finally, the content is not removed from the flow, so it consumes as much space as it needs, and no more.

CSS notes

The tab-switching trick is much the same as the canonical input:checked + * method. The essential layout trick with grid is to force all the tabs onto the top row, then assign the bar's remaining width to an empty column for the content to span across.

[1] Unfortunately, while styling this with grid-column: 1 / -1 ("span the first grid-line to the last grid-line") should theoretically work, it does not. Implicitly-created columns are not included in the calculation, so it only spans the explicitly created columns (a grand total of 1, in this case). Spanning 1 more grid-column than you have tabs is the minimum to make this work.

[2] Disabling pointer-events on the <summary> element of the open <dialog> forces a user to keep one tab open at all times.

[3] By using the :not() selector, the tab CSS leaves me free to set whatever display property I want on the tab content.

Reasons:
  • RegEx Blacklisted phrase (1): I want
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • Low reputation (0.5):
Posted by: David