CSS flip card: How can I activate the flip via tab, so it is keyboard accessible?

Viewed 2322

I have found some great tuts on how to make a flip card with CSS. My question though is how to make this keyboard accessible. In other words, for a user with a disability who only is using a keyboard, hopefully they would be able to just use the tab button (thus focus), and the card would turn over to show the back content and allow tabbing to select links on the card's back.

I Googled this and found some suggestions (please see the jsfiddle below where I tried them), but I couldn't get success.

Here is a great website that has this functionality, but I don't know how they made it work: https://businessexpress.maryland.gov/

Notice that if you hold tab down on the above page, eventually your cards will flip, and you can then tab through the links on them. For example, for the first flip card there is a link "/plan", and then it has sublinks like "/plan/create-business-plan", etc.

Please notice that I tried to put in some CSS on line 21 that would affect the "active" and "focus" pseudo classes. But only hovering makes the card flip. I wish that tabbing onto any of the links would flip the card, like in the maryland.gov example above.

I've included a jsfiddle here (there is a little input element so you can start tabbing from it): https://jsfiddle.net/anrbhcmv/

HTML:

    <div id="content">
        <h1>Small Business Resources</h1>

    <input type="text">
    <br><br>

        <div class="flip-card">
            <div class="flip-card-inner">
                <a href="#" id="flip-card-inner">
                    <div class="flip-card-front">
                        <div>Card-front content</div>
                    </div>
                </a>
                <div class="flip-card-back">
                    <a href="https://www.google.com">Google</a>
                    <div>Text</div>
                </div>
            </div>
        </div>

    </div><!-- end #content -->

CSS:

/* The flip card container - set the width and height to whatever you want. We have added the border property to demonstrate that the flip itself goes out of the box on hover (remove perspective if you don't want the 3D effect */
.flip-card {
    background-color: transparent;
    width: 300px;
    height: 200px;
    // border: 1px solid #f1f1f1;
    // perspective: 1000px; /* Remove this if you don't want the 3D effect */
}

/* This container is needed to position the front and back side */
.flip-card-inner {
    position: relative;
    width: 100%;
    height: 100%;
    text-align: center;
    transition: transform 0.8s;
    transform-style: preserve-3d;
}

/* Do an horizontal flip when you move the mouse over the flip box container */
.flip-card:hover .flip-card-inner, .flip-card:active .flip-card-inner, .flip-card:focus .flip-card-inner{
    transform: rotateY(180deg);
}

/* Position the front and back side */
.flip-card-front, .flip-card-back {
    position: absolute;
    width: 100%;
    height: 100%;
    -webkit-backface-visibility: hidden; /* Safari */
    backface-visibility: hidden;
}

/* Style the front side (fallback if image is missing) */
.flip-card-front {
    background-color: #008CCC;
    background-color: azure;
    color: white;
    color: black;
}

/* Style the back side */
.flip-card-back {
    background-color: #99CC66;
    color: white;
    transform: rotateY(180deg);
}
2 Answers

You can use :focus-within pseudo-class:

.flip-card:focus-within .flip-card-inner

/* The flip card container - set the width and height to whatever you want. We have added the border property to demonstrate that the flip itself goes out of the box on hover (remove perspective if you don't want the 3D effect */

.flip-card {
  background-color: transparent;
  width: 300px;
  height: 200px;
  // border: 1px solid #f1f1f1;
  // perspective: 1000px; /* Remove this if you don't want the 3D effect */
}


/* This container is needed to position the front and back side */

.flip-card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: transform 0.8s;
  transform-style: preserve-3d;
}


/* Do an horizontal flip when you move the mouse over the flip box container */

.flip-card:hover .flip-card-inner,
.flip-card:focus-within .flip-card-inner,
.flip-card:active .flip-card-inner,
.flip-card:focus .flip-card-inner {
  transform: rotateY(180deg);
}


/* Position the front and back side */

.flip-card-front,
.flip-card-back {
  position: absolute;
  width: 100%;
  height: 100%;
  -webkit-backface-visibility: hidden;
  /* Safari */
  backface-visibility: hidden;
}


/* Style the front side (fallback if image is missing) */

.flip-card-front {
  background-color: #008CCC;
  background-color: azure;
  color: white;
  color: black;
}


/* Style the back side */

.flip-card-back {
  background-color: #99CC66;
  color: white;
  transform: rotateY(180deg);
}
<div id="content">
  <h1>Small Business Resources</h1>

  <input type="text">
  <br><br>

  <div class="flip-card">
    <div class="flip-card-inner">
      <a href="#" id="flip-card-inner">
        <div class="flip-card-front">
          <div>Card-front content</div>
        </div>
      </a>
      <div class="flip-card-back">
        <a href="https://www.google.com">Google</a>
        <div>Text</div>
      </div>
    </div>
  </div>

</div>
<!-- end #content -->

Please do not just use focus-within as suggested(*).

It has no support in Internet Explorer, which is a big problem as this question is marked as "accessibility".

Whereas Internet Explorer support wouldn't normally be a problem, around 11% of screen reader users use IE 11 and a further 1.5% use IE 9 and 10..

Also please be aware that around 25% of screen reader users are not blind. so making sure the visual experience matches the screen reader experience is also important (in case you were wondering why it matters if the card rotates for a screen reader user.)

Below is a very rough idea for a workaround that should be fairly flexible, it does rely on the parent item having a unique class as it stands but that could easily be fixed if you have lots of these, it is only an example to get you started.

The below code should be compatible all the way back to IE9 (please check I may have made a minor mistake), increasing your browser coverage from 89% to around 97%, a 10% improvement.

CSS - please also check the adjustments to the CSS, I added two separate items, one is the .flip-card.focus-within .flip-card-inner selector for the class we add to the parent to perform the rotation.

The other is @media (prefers-reduced-motion) to switch off the rotation animation if a user has set their user settings to 'reduced animation', another accessibility improvement.

It is not often I advocate for a JavaScript solution over a CSS one but in this scenario we are still limited by older browsers I am afraid.

function addListeners(parentClass){
    var focusableItems = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', '[tabindex="0"]'];
    for (i = 0, leni = focusableItems.length; i < leni; i++) {
        var focusableElements = document.querySelectorAll("." + parentClass + " " + focusableItems[i]);
        for (j = 0, lenj = focusableElements.length; j < lenj; j++) {
            focusableElements[j].addEventListener("focus", function() {
              document.querySelector("." + parentClass).classList.add("focus-within");
            });
            focusableElements[j].addEventListener("blur", function() {
              document.querySelector("." + parentClass).classList.remove("focus-within");
            });
        }
    }
}
addListeners("flip-card");
/* The flip card container - set the width and height to whatever you want. We have added the border property to demonstrate that the flip itself goes out of the box on hover (remove perspective if you don't want the 3D effect */
.flip-card {
    background-color: transparent;
    width: 300px;
    height: 200px;
    // border: 1px solid #f1f1f1;
    // perspective: 1000px; /* Remove this if you don't want the 3D effect */
}

/* This container is needed to position the front and back side */
.flip-card-inner {
    position: relative;
    width: 100%;
    height: 100%;
    text-align: center;
    transition: transform 0.8s;
    transform-style: preserve-3d;
}

/* Do an horizontal flip when you move the mouse over the flip box container */
.flip-card:hover .flip-card-inner, .flip-card:active .flip-card-inner, .flip-card:focus .flip-card-inner{
    transform: rotateY(180deg);
}

/* Position the front and back side */
.flip-card-front, .flip-card-back {
    position: absolute;
    width: 100%;
    height: 100%;
    -webkit-backface-visibility: hidden; /* Safari */
    backface-visibility: hidden;
}

/* Style the front side (fallback if image is missing) */
.flip-card-front {
    background-color: #008CCC;
    background-color: azure;
    color: white;
    color: black;
}

/* Style the back side */
.flip-card-back {
    background-color: #99CC66;
    color: white;
    transform: rotateY(180deg);
}

/* New CSS selector */
.flip-card.focus-within .flip-card-inner{
    transform: rotateY(180deg);
}

@media (prefers-reduced-motion) {
    .flip-card-inner {
        transition: transform none;
    }

}
<div id="content">
        <h1>Small Business Resources</h1>

    <input type="text">
    <br><br>

        <div class="flip-card">
            <div class="flip-card-inner">
                <a href="#" id="flip-card-inner">
                    <div class="flip-card-front">
                        <div>Card-front content</div>
                    </div>
                </a>
                <div class="flip-card-back">
                    <a href="https://www.google.com">Google</a>
                    <div>Text</div>
                </div>
            </div>
        </div>

    </div><!-- end #content -->

(*) Alternative route with polyfill

I found a focus-within polyfill, I haven't checked whether it would work in IE9 and 10 but I would consider it a reasonable compromise to only support IE11 for most companies and it appears to work in IE11.

We still support back to IE9 where we can, hence why I included the above code snippet (plus it is less JS so better for performance).

Related