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; | |
| } |