A card pattern for a user dashboard using flexbox for grid layout. Keyboard accessible, a11y friendly, and built using Progressive Enhancement.
A Pen by Jerry Jones on CodePen.
<!-- symbol defs--> | |
<svg style="position: absolute; width: 0; height: 0;" width="0" height="0" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |
<defs> | |
<svg id="icon-chevron" viewBox="0 0 1024 1024"> | |
<title>Click to Show or Hide Menu</title> | |
<path class="path1" d="M316 334l196 196 196-196 60 60-256 256-256-256z"></path> | |
</svg> | |
</defs> | |
</svg> | |
<ul class="dash-list"> | |
<li id="dash-item--1" class="dash-item dash-item--published"> | |
<div class="dash-item__header"> | |
<h3 class="dash-item__title"><a href="#">Semantic HTML Quiz</a></h3> | |
<ul id="dash-item__nav--1" class="dash-item__nav"> | |
<li class="dash-item__nav__item"><a href="#">Results</a></li><li class="dash-item__nav__item"><a href="#">Settings</a></li><li class="dash-item__nav__item"><a href="#">Embed</a></li><li class="dash-item__nav__item"><a href="#">Delete</a></li> | |
</ul> | |
</div> | |
<div class="dash-item__content"> | |
<ul class="quiz-results"> | |
<li class="quiz-results__item quiz-results__item--views"> | |
<span class="quiz-results__number quiz-results__number--views">832</span> | |
<div class="quiz-results__label">Views</div> | |
</li> | |
<li class="quiz-results__item quiz-results__item--finishes"> | |
<span class="quiz-results__number quiz-results__number--finishes">588</span> | |
<div class="quiz-results__label">Finishes</div> | |
</li> | |
<li class="quiz-results__item quiz-results__item--average-score"> | |
<span class="quiz-results__number quiz-results__number--average-score">80</span> | |
<div class="quiz-results__label">Average</div> | |
</li> | |
</ul> | |
</div> | |
</li> | |
<li id="dash-item--2" class="dash-item dash-item--published"> | |
<div class="dash-item__header"> | |
<h3 class="dash-item__title"><a href="#">How well do you know CSS?</a></h3> | |
<ul id="dash-item__nav--2" class="dash-item__nav"> | |
<li class="dash-item__nav__item"><a href="#">Results</a></li><li class="dash-item__nav__item"><a href="#">Settings</a></li><li class="dash-item__nav__item"><a href="#">Embed</a></li><li class="dash-item__nav__item dash-item__nav__item--delete"><a href="#">Delete</a></li> | |
</ul> | |
</div> | |
<div class="dash-item__content"> | |
<ul class="quiz-results"> | |
<li class="quiz-results__item quiz-results__item--views"> | |
<span class="quiz-results__number quiz-results__number--views">938</span> | |
<div class="quiz-results__label">Views</div> | |
</li> | |
<li class="quiz-results__item quiz-results__item--finishes"> | |
<span class="quiz-results__number quiz-results__number--finishes">856</span> | |
<div class="quiz-results__label">Finishes</div> | |
</li> | |
<li class="quiz-results__item quiz-results__item--average-score"> | |
<span class="quiz-results__number quiz-results__number--average-score">90</span> | |
<div class="quiz-results__label">Average</div> | |
</li> | |
</ul> | |
</div> | |
</li> | |
<li id="dash-item--3" class="dash-item dash-item--draft"> | |
<div class="dash-item__header"> | |
<h3 class="dash-item__title"><a href="#">Draft: Test Your JS Skills</a></h3> | |
<ul id="dash-item__nav--3" class="dash-item__nav"> | |
<li class="dash-item__nav__item"><a href="#">Edit</a></li><li class="dash-item__nav__item"><a href="#">Preview</a></li><li class="dash-item__nav__item"><a href="#">Delete</a></li> | |
</ul> | |
</div> | |
<div class="dash-item__content"> | |
<ul class="quiz-results"> | |
<li class="quiz-results__item quiz-results__item--views"> | |
<span class="quiz-results__number quiz-results__number--views">0</span> | |
<div class="quiz-results__label">Views</div> | |
</li> | |
<li class="quiz-results__item quiz-results__item--finishes"> | |
<span class="quiz-results__number quiz-results__number--finishes">0</span> | |
<div class="quiz-results__label">Finishes</div> | |
</li> | |
<li class="quiz-results__item quiz-results__item--average-score"> | |
<span class="quiz-results__number quiz-results__number--average-score">0</span> | |
<div class="quiz-results__label">Average</div> | |
</li> | |
</ul> | |
</div> | |
</li> | |
</ul> | |
<p><a class="twitter__username-link" href="https://twitter.com/juryjowns" target="_top">@juryjowns</a></p> | |
jQuery( document ).ready( function( $ ) { | |
// add our class to the action menu to set the styles if javascript is enabled | |
$('.dash-item__nav').each(function() { | |
$(this).addClass('dash-item__nav--collapsible') | |
.attr('aria-hidden', true) | |
.before('<button class="dash-item__menu-action" type="button" aria-expanded="false" aria-controls="'+$(this).attr('id')+'"><svg class="dash-item__menu-action__icon dash-item__menu-action__icon--bottom"><use xlink:href="#icon-chevron" /></svg><svg class="dash-item__menu-action__icon dash-item__menu-action__icon--top"><use xlink:href="#icon-chevron" /></svg></button>'); | |
}); | |
$(document).on('click', '.dash-item__menu-action', function() { | |
var dashItem = $(this).closest('.dash-item'); | |
if(dashItem.hasClass('dash-item--menu-active')) { | |
// remove states from the active menu item | |
removeActiveMenuStates(dashItem); | |
$('body').removeClass('dash-list--focus-one'); | |
} else { | |
// remove states from any active menu item, if there is one | |
var previouslyActiveMenu = $('.dash-item--menu-active'); | |
if(0 < previouslyActiveMenu.length ) { | |
removeActiveMenuStates(previouslyActiveMenu); | |
} | |
// add in the active states to the clicked dashItem | |
addActiveMenuStates(dashItem); | |
// add a class to the body telling that there's a dashItem active | |
$('body').addClass('dash-list--focus-one'); | |
// move focus to first item in newly opened menu | |
$('.dash-item__nav__item:eq(0) a', dashItem).focus(); | |
} | |
}); | |
function addActiveMenuStates(dashItem) { | |
// add the new active states in | |
dashItem.addClass('dash-item--menu-active'); | |
// button to activate the menu | |
$('.dash-item__menu-action', dashItem).attr('aria-expanded', true); | |
// menu | |
$('.dash-item__nav', dashItem).attr('aria-hidden', false); | |
} | |
function removeActiveMenuStates(dashItem) { | |
// dash item card | |
dashItem.removeClass('dash-item--menu-active'); | |
// button to activate the menu | |
$('.dash-item__menu-action', dashItem).attr('aria-expanded', false) | |
// menu | |
$('.dash-item__nav', dashItem).attr('aria-hidden', true); | |
} | |
}); |
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> |
$green: darken(#58C88F, 10); | |
$blue: darken(#5887C0, 7.5); | |
$link: $blue; | |
$fastInEaseOut: cubic-bezier(0.000, 0, .3, 1); | |
body { | |
background: #e9f3f0; | |
max-width: 70rem; | |
margin: 0 auto; | |
padding: 2rem 20px 3rem; | |
font-family: "ff-tisa-sans-web-pro", Trebuchet MS,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Tahoma, sans-serif; | |
transition: all .2s $fastInEaseOut; | |
} | |
a { | |
color: $link; | |
text-decoration: none; | |
transition: color .2s; | |
touch-action: manipulation; | |
&:focus { | |
outline: 1px dotted $link; | |
} | |
&:hover, | |
&:focus { | |
color: darken($link, 12.5); | |
} | |
} | |
.dash-list { | |
display: flex; | |
flex-flow: row wrap; | |
align-items: stretch; | |
list-style: none; | |
margin-left: 0; | |
} | |
.dash-item { | |
display: flex; | |
flex-direction: column; | |
align-content: flex-end; | |
justify-content: flex-end; | |
padding: 1.2rem 1.4rem 1rem; | |
background: #fff; | |
list-style: none; | |
transition: all .5s; | |
margin: 0 0 0.8rem; | |
position: relative; | |
overflow: hidden; | |
transition: all .2s $fastInEaseOut; | |
@media (min-width: 500px) { | |
flex-basis: 49%; | |
margin: 0 2% 0.8rem 0; | |
&:nth-child(2n) { | |
margin-right: 0; | |
} | |
} | |
@media (min-width: 800px) { | |
flex-basis: 28%; | |
margin-right: 1.25%; | |
&:nth-child(2n) { | |
margin-right: 1.25%; | |
} | |
&:nth-child(3n) { | |
margin-right: 0; | |
} | |
} | |
} | |
.dash-item--published { | |
box-shadow: inset 4px 0 0 $green, 0 1px 0 rgba(0,0,0,.1); | |
} | |
.dash-item--draft { | |
box-shadow: inset 4px 0 0 lighten($green, 25%), 0 1px 0 rgba(0,0,0,.1); | |
} | |
.dash-item__header { | |
position: relative; | |
z-index: 99; | |
background: #fff; | |
border-bottom: 1px solid #ddd; | |
} | |
.dash-item__title { | |
font-size: 1rem; | |
padding: 0 24px 0.5rem 0; | |
margin: 0; | |
} | |
.dash-item__content { | |
padding: 1.8rem 0 0.375rem; | |
position: relative; | |
transition: all .2s $fastInEaseOut; | |
} | |
.dash-item__status { | |
font-size: .7rem; | |
font-weight: 300; | |
text-transform: uppercase; | |
color: #888; | |
position: absolute; | |
} | |
.dash-item__nav { | |
position: absolute; | |
right: 1rem; | |
list-style: none; | |
margin: 0; | |
padding: 0; | |
} | |
.dash-item__nav__item { | |
display: inline-block; | |
font-size: 0.85rem; | |
margin-bottom: 0; | |
padding-bottom: 0; | |
margin-right: 10px; | |
&:last-of-type { | |
margin-right: 0; | |
} | |
} | |
.dash-item__nav--collapsible { | |
display: none; | |
position: absolute; | |
background: #fff; | |
bottom: auto; | |
right: auto; | |
top: 100%; | |
margin-top: 0.2rem; | |
transform: translate3d(0,0,0); | |
transition: all .2s $fastInEaseOut; | |
.dash-item__nav__item { | |
padding: 0.4rem 0; | |
} | |
} | |
.dash-item__menu-action { | |
background: none; | |
border: none; | |
color: #444; | |
padding: 0; | |
position: absolute; | |
bottom: 0; | |
right: 0; | |
cursor: pointer; | |
} | |
.dash-item__menu-action__icon { | |
width: 24px; | |
height: 24px; | |
fill: #444; | |
transition: all .3s $fastInEaseOut; | |
} | |
.dash-item__menu-action__icon--bottom { | |
position: absolute; | |
left: 0; | |
} | |
.dash-list--focus-one { | |
background: #bcd4ce; | |
.dash-item { | |
opacity: 0.675; | |
} | |
.dash-item--menu-active { | |
transform: translate3d(0,-3px,0); | |
box-shadow: inset 4px 0 0 $green, 0 2px 2px rgba(0,0,0,.2); | |
opacity: 1; | |
} | |
} | |
.dash-item--menu-active { | |
.dash-item__menu-action__icon--bottom { | |
transform: translateY(-15.5%); | |
} | |
.dash-item__menu-action__icon--top { | |
transform: rotateX(-180deg) translateY(0px); | |
} | |
.dash-item__nav--collapsible { | |
display: block; | |
animation: slideInTop .25s $fastInEaseOut forwards; | |
} | |
.dash-item__content { | |
opacity: 0.4; | |
transform: translate3d(0, 0.675rem, 0); | |
} | |
} | |
.quiz-results { | |
display: flex; | |
justify-content: space-between; | |
list-style: none; | |
margin: 0; | |
padding: 0; | |
text-align: center; | |
} | |
.quiz-results__item { | |
margin: 0 2% 0 0; | |
padding: 0; | |
color: #454545; | |
@media (max-width:280px) { | |
width: 100%; | |
padding: 0; | |
margin-bottom: 0.6rem; | |
} | |
} | |
.quiz-results__number { | |
font-size: 1.6rem; | |
line-height: 1; | |
position: relative; | |
} | |
.dash-item--draft .quiz-results__number { | |
opacity: 0.5; | |
} | |
.quiz-results__number--average-score { | |
color: darken($green, 8); | |
&:after { | |
content: '%'; | |
font-size: .9rem; | |
position: absolute; | |
top: .3rem; | |
right: -0.6rem; | |
} | |
} | |
.quiz-results__label { | |
text-transform: uppercase; | |
font-size: .7rem; | |
font-weight: 300; | |
color: #888; | |
} | |
@keyframes slideInTop { | |
0% { | |
opacity: 0; | |
transform: translate3d(0, -20px, 0); | |
} | |
100% { | |
opacity: 1; | |
transform: translate3d(0, 0, 0); | |
} | |
} | |
.twitter__username-link { | |
position: relative; | |
top: 2rem; | |
} |