<h1>Fork: Playing with CSS Grid <code>auto-fit</code> and <code>grid-auto-flow</code></h1>
<p>
This pen is forked from <a href="https://codepen.io/basherkev/pen/rNwpwgP">the example</a> used in the CSS-Tricks article <cite><a href="https://css-tricks.com/expandable-sections-within-a-css-grid/">Expandable Sections Within a CSS Grid</a></cite>. I made the following changes:
</p>
<ul>
<li>Removed the <code>region</code> role, which did nothing without an accName;</li>
<li>Added <code>aria-expanded</code> to the trigger so it conveys its purpose (else a likely 4.1.2 failure);</li>
<li>Removed the live region, which is problematic since it double-announced (once from the live region, once from setting focus to it) and does not convey structure of children;</li>
<li>Re-wrote function (my code is meh) to focus only on toggling <code>aria-expanded</code> and the one class already in use;</li>
<li>Amended function for each button to act as its own disclosure toggle;</li>
<li>Removed close buttons in each expanded area, though if testing with users tells you they should be there, then restore them;</li>
<li>Added feature to script to allow you to open new items without closing other items (toggle the button following this list);</li>
<li>Set the always-visible grid items to the same minimum height (looked weird at two columns and <code>grid-auto-rows</code> on the grid container made everything resize when the new content was shown).</li>
</ul>
<p class="toggles">
<button type="button" id="multi" aria-pressed="false" onclick="toggle(this.id);">
Allow multiple cards to be expanded
</button>
</p>
<p>
There is more information about <a href="https://adrianroselli.com/2020/05/disclosure-widgets.html">disclosure widgets</a> on my site, a <a href="https://codepen.io/aardrian/pen/NWpoVQd">sample toggle tip</a> (which is almost this but without the nifty grid layout, and it closes on <kbd>Esc</kbd>), and <a href="https://adrianroselli.com/2019/06/link-disclosure-widget-navigation.html">dislosure widgets as an accessible navigation pattern</a>.
</p>
<div>
<ul class="grid">
<li>
<p>1</p>
</li>
<li>
<p>2</p>
<button type="button" id="btnDisc02" aria-expanded="false" onclick="toggleDisclosure(this.id);" aria-controls="Disclosed02">Quick view</button>
</li>
<li class="fullwidth is-hidden" id="Disclosed02">
<p>fullwidth 2</p>
<p>This grid item needs to stretch the full width of the page, and force other grid items to reflow around it, whilst remaining "visually connected" to the preceeding grid item.</p>
<p>Test <a href="#">inline link</a>.</p>
</li>
<li>
<p>3</p>
</li>
<li>
<p>4</p>
<button type="button" id="btnDisc04" aria-expanded="false" onclick="toggleDisclosure(this.id);" aria-controls="Disclosed04">Quick view</button>
</li>
<li class="fullwidth is-hidden" id="Disclosed04">
<p>fullwidth 4</p>
<p>This grid item needs to stretch the full width of the page, and force other grid items to reflow around it, whilst remaining "visually connected" to the preceeding grid item.</p>
<p>Test <a href="#">inline link</a>.</p>
</li>
<li>
<p>5</p>
<button type="button" id="btnDisc05" aria-expanded="false" onclick="toggleDisclosure(this.id);" aria-controls="Disclosed05">Quick view</button>
</li>
<li class="fullwidth is-hidden" id="Disclosed05">
<p>fullwidth 5</p>
<p>This grid item needs to stretch the full width of the page, and force other grid items to reflow around it, whilst remaining "visually connected" to the preceeding grid item.</p>
<p>Test <a href="#">inline link</a>.</p>
</li>
<li>
<p>6</p>
</li>
<li>
<p>7</p>
</li>
<li>
<p>8</p>
</li>
</ul>
</div>
ul[class] {
margin: 0;
padding: 0;
}
ul[class] li {
list-style: none;
}
ul[class] li > * {
margin: 1rem;
}
:focus {
box-shadow: 0 0 0 0.25rem rebeccapurple;
outline: 0;
}
/* [1] 'auto-fit' grid columns, so no media queries required. */
/* [2] 'dense' packing fills in holes earlier in the grid. */
.grid {
display: grid;
gap: 1rem;
grid-auto-flow: dense; /* [2] */
grid-template-columns: repeat(auto-fit, 20rem); /* [1] */
justify-content: center;
/* AAR: Will not work well with expanded data, so do not use */
/* grid-auto-rows: 7rem; */
}
.grid > * {
align-items: flex-start;
background: #eee;
display: flex;
flex-direction: column;
height: 100%;
/* AAR: added so all items are same height (try with two cols) */
min-height: 7rem;
}
/* [3] Make fullwidth card span all grid columns. */
.fullwidth {
grid-column: 1 / -1;
}
.is-hidden {
display: none;
}
.fullwidth,
.is-selected {
background: wheat;
}
/* I just dumped this in from another pen, */
/* it is over-engineered for this case so */
/* you should probably ignore it. */
/* Toggle */
/* https://adrianroselli.com/2019/08/under-engineered-toggles-too.html */
.toggles {
/* margin: 0;
padding: .5em 2em 1em 2em;
border-bottom: .1em solid #ccc;
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: #fff; */
}
.toggles [aria-pressed] {
display: block;
box-sizing: border-box;
border: none;
color: inherit;
background: none;
font: inherit;
line-height: inherit;
text-align: left;
padding: .4em 0 .4em 4em;
position: relative;
}
.toggles [aria-pressed][disabled],
.toggles [aria-pressed][disabled]:hover {
color: #999;
}
.toggles [aria-pressed]:focus,
.toggles [aria-pressed]:hover {
color: #00f;
outline: none;
}
.toggles [aria-pressed]:focus::before,
.toggles [aria-pressed]:hover::before {
box-shadow: 0 0 0.5em #333;
}
.toggles [aria-pressed]:focus::after,
.toggles [aria-pressed]:hover::after {
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='50' cy='50' r='50' fill='rgba(0,0,0,.25)'/%3E%3C/svg%3E");
background-size: 30%;
background-repeat: no-repeat;
background-position: center center;
}
.toggles [aria-pressed]::before,
.toggles [aria-pressed]::after {
content: "";
position: absolute;
height: 1.5em;
transition: all 0.25s ease;
}
.toggles [aria-pressed]::before {
left: 0;
top: 0.2em;
width: 3em;
border: 0.2em solid #767676;
background: #767676;
border-radius: 1.1em;
}
.toggles [aria-pressed]::after {
left: 0;
top: 0.25em;
background-color: #fff;
background-position: center center;
border-radius: 50%;
width: 1.5em;
border: 0.15em solid #767676;
}
.toggles [aria-pressed=true]::after {
left: 1.6em;
border-color: #36a829;
color: #36a829;
}
.toggles [aria-pressed=true]::before {
background-color: #36a829;
border-color: #36a829;
}
.toggles [aria-pressed][disabled]::before {
background-color: transparent;
border-color: #ddd;
}
.toggles [aria-pressed][disabled]::after {
border-color: #ddd;
}
.toggles [aria-pressed][disabled]:hover {
color: #999; /* case for CSS custom property if not supporting IE/Edge */
}
.toggles [aria-pressed][disabled]:hover::before {
box-shadow: none;
}
.toggles [aria-pressed][disabled]:hover::after {
background-image: none;
}
/* Put toggles on the right like the iOS the kids like */
.toggles.flip [aria-pressed]::before,
.toggles.flip [aria-pressed]::after {
left: auto;
right: 0;
}
.toggles.flip [aria-pressed]::after {
left: auto;
right: 1.6em;
}
.toggles.flip [aria-pressed=true]::after {
right: 0;
}
.toggles.flip [aria-pressed] {
padding-left: 0;
padding-right: 4em;
}
/* Windows High Contrast Mode Support */
@media screen and (-ms-high-contrast: active) {
.toggles [aria-pressed]:focus::before,
.toggles [aria-pressed]:hover::before {
outline: 1px dotted windowText;
outline-offset: 0.25em;
}
.toggles [aria-pressed]::after {
background-color: windowText;
}
.toggles [aria-pressed][disabled]::after {
background-color: transparent;
}
}
/* Reduced motion */
@media screen and (prefers-reduced-motion: reduce) {
.toggles [aria-pressed]::before,
.toggles [aria-pressed]::after {
transition: none;
}
}
/* RTL */
/* https://twitter.com/dror3go/status/1102946375396982784 */
*[dir="rtl"] .toggles [aria-pressed] {
padding-left: 0;
padding-right: 4em;
}
*[dir="rtl"] .toggles [aria-pressed]::before,
*[dir="rtl"] .toggles [aria-pressed]::after {
left: auto;
right: 0;
}
*[dir="rtl"] .toggles [aria-pressed]::after {
right: 0;
}
*[dir="rtl"] .toggles [aria-pressed=true]::after {
right: 1.6em;
}
/* Put toggles on the right like the iOS the kids like */
*[dir="rtl"] .toggles.flip [aria-pressed]::before,
*[dir="rtl"] .toggles.flip [aria-pressed]::after {
left: 0;
right: auto;
}
*[dir="rtl"] .toggles.flip [aria-pressed]::after {
right: auto;
left: 1.6em;
}
*[dir="rtl"] .toggles.flip [aria-pressed=true]::after {
left: 0;
}
*[dir="rtl"] .toggles.flip [aria-pressed] {
padding-right: 0;
padding-left: 4em;
}
// For the ability to choose if multiple cards
// can be expanded at the same time.
var CloseAll = true;
function toggleDisclosure(btnID) {
// Change value to false if you want each opened view
// to stay open as you open more (test the grid).
// var CloseAll = true;
if (CloseAll == true) {
toggleAll(btnID);
} else {
toggleOne(btnID);
}
}
function toggleAll(btnID) {
// Get the button that triggered this
var theButton = document.getElementById(btnID);
// Get its state
var theButtonState = theButton.getAttribute("aria-expanded");
// Get the value of the aria-controls attribute
var strControls = theButton.getAttribute("aria-controls");
// Find the node with that id
var theDisclosee = document.getElementById(strControls);
// Get all the disclosure buttons
var allButtons = document.querySelectorAll("[aria-expanded]");
// Get all the disclosure content
var allDisclosees = document.querySelectorAll(".fullwidth");
// Loop through all buttons and mark as closed
for (var i = 0; i < allButtons.length; i++) {
allButtons[i].setAttribute("aria-expanded", "false");
}
// Loop through all disclosees and add hidden class
for (var i = 0; i < allDisclosees.length; i++) {
allDisclosees[i].classList.add('is-hidden');
}
// If the button was not expanded...
if (theButtonState == "false") {
// Now set the button to expanded
theButton.setAttribute("aria-expanded", "true");
// Remove the is-hidden class
theDisclosee.classList.remove('is-hidden');
// Otherwise button was expanded...
} else {
// Now set the button to collapsed
theButton.setAttribute("aria-expanded", "false");
// Add the class that hides the content
theDisclosee.classList.add('is-hidden');
}
}
function toggleOne(btnID) {
// Get the button that triggered this
var theButton = document.getElementById(btnID);
// Get the value of the aria-controls attribute
var strControls = theButton.getAttribute("aria-controls");
// Find the node with that id
var theDisclosee = document.getElementById(strControls);
// If the button is not expanded...
if (theButton.getAttribute("aria-expanded") == "false") {
// Now set the button to expanded
theButton.setAttribute("aria-expanded", "true");
// Remove the is-hidden class
theDisclosee.classList.remove('is-hidden');
// Otherwise button is expanded...
} else {
// Now set the button to collapsed
theButton.setAttribute("aria-expanded", "false");
// Add the class that hides the content
theDisclosee.classList.add('is-hidden');
}
}
// Just for the toggle control I added. It has no
// bearing on the card display thinger.
function toggle(btnID) {
var theButton = document.getElementById(btnID);
if (theButton.getAttribute("aria-pressed") == "false") {
theButton.setAttribute("aria-pressed", "true");
CloseAll = false;
} else {
theButton.setAttribute("aria-pressed", "false");
CloseAll = true;
}
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.