Recently, I found myself craving to make something interesting. So I sat down and scrolled through dribbble to see if there was anything on there that I'd like to make. I came across a card that could be swiped to reveal some action buttons and set to work.
As you've, no doubt, gathered I'll be building a reveal card, I have no idea if they have an official name, in this post but I must preface this with, I'm not an A11y expert I like to think I have some idea what I'm doing but I could well have got some of this wrong. If you notice something wrong or want to ask questions about why I've done certain things feel free to leave a comment and I'll get back to you.
This is what I'll be making. It works with mouse, touch, keyboard and, I hope, screen readers read from it correctly too.
The structure
For the structure, which we will later describe in HTML, we'll need a base that can be tabbed to, a layer for options, or buttons in our case, and the info card that sits on top of the options obscuring them until we slide them out of the way.
For a11y think the base should also have aria-expanded
to indicate the section can be expanded also the buttons should have aria-hidden
on them or a wrapper to prevent screen readers from reading them.
I think something like that matches my description but really doesn't look great as of yet. Which neatly leads us on to styling.
The look
For the look I'll be sticking to a quite material design, which means we'll use open sans fonts, buttons are transparent circles that become translucent on mouse over and we have a box shadow on the while thing. Quite simple when you explain it like that really isn't it. Here's the SCSS:
Whilst this now looks passable there are a few things to consider, the card is white but behind it needs colour too and we really should let potential users pick from a few colours, the default outline is ok but it different across browsers and doesn't really look that great and finally other than the cursor changing there's no real indication to the user that the panel slides.
Adding variants
You may have noticed in the above SCSS I was using BEM which is just a style guide. In BEM when you add a variant you use --
followed by the variant name. So let's add success, info and warn as our three colour variants.
Because we're using SCSS we can easily just add the variants to our material-slide
styles like so.
.material-slide {
position: relative;
margin: 0.8em 0;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
overflow: hidden;
&--success {
background-color: #1B5E20;
color: #fefefe;
}
&--info {
background-color: #0D47A1;
color: #fefefe;
}
&--warn {
background-color: #B71C1C;
color: #fefefe;
}
}
I've stated the background colour and the text colour so if someone wants to add a new variant to the code they can follow the pattern and add a better contrast colour if they need one.
Nice outline
Now this is quite an interesting topic lots of A11y purest say the default outline should be left alone and for a long time I agreed with that but over the years I've considered alternatives and I think as long as you let the user know they have focus of your element it's ok to be a little adventurous.
The method I've been using for a while now is to use a box shadow in an after, though in this case I've had to use a div, which has opacity 0 until the element is focus-visible
.
Again we add this to the material-slide
styles.
// inside .material-slide
& .material-slide__outline {
content: '';
z-index: 2;
border-radius: 4px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
box-shadow: inset 0 0 0 1px #fefefe, inset 0 0 0 3px #01579B, inset 0 0 0 4px #fefefe;
opacity: 0;
}
// inside .material-slide__options
&:focus-visible {
outline: none;
& ~ .material-slide__outline {
opacity: 1;
}
}
The outline is made of three lines a two white ones sandwiching a blue one, this helps with keeping the outline noticeable on top of different colours.
Gesture hints
Finally for the styles I don't think it's obvious when you see a card that it can be dragged even if you cursor changes to 'grab'. I had a think about this for a while and decided that if someone hovers over the card we should move it over by 5px just so show that the card moves.
To do this I simply added the transform to hover inside material-slide__info
.
&:hover:not(.material-slide__options--user-control) {
transform: translatex(-5px);
}
You'll also notice there is material-slide__options--user-control
this is a class we will be adding with JS so let's move on to that now.
The functionality
We've made it to the final part adding some JS to make the whole thing function. Mostly this will be event listeners with a little bit of state management. For the listeners we'll want to listen for keyboard events, mouse events and touch events.
State management
For this I will just have an object which contains all the variables I need, then I will update and check against that object as I need to.
const state = {
isActive: false,
isOpen: false,
isOpenLast: false,
startPos: null,
currentPos: null,
}
Event listeners
I'll need to listen for mouse down, move, leave and up to keep track of how far the card has been dragged. I've also decided to add double click to open an close fortunately there is a event for that. For keyboard support I'll just need to listen of key up and for touch I'll listen for touch start, move, end and cancel which are basically the same as mouse but slightly different (because of multitouch).
Sign off
Well there we have it we've made a cool little sliding panel that we can hide buttons behind. We've made it so a keyboard user can use it and so that a screen reader can read it.
I was thinking I might do another part to this series where I convert this to a React component and put it on Git/NPM, would there be any interest for that?
Thank you for reading to the end I hope you got something from this and as I said at the top of the article feel free to leave any comments or come shout at me over on twitter.
Thanks again 🧠🧙♂️❤️🦄🧠🦄❤️🧠🧙♂️🧠🧙♂️
Top comments (11)
Unfortunately this pattern won't work (make that doesn't work, I did give it a quick try) properly with a screen reader.
It breaks the ability to open the buttons once a screen reader is involved due to how the click handler behaves.
Also you will never get the "expanded" state announced as it is not valid on a
<div>
, you could possibly remedy this withrole="button"
on the<div>
but then you have a big issue with nested buttons.In fact nested interactive elements is something that should be avoided as it can cause havoc with click handlers once a screen reader gets involved.
The other issue is that
aria-hidden=true
will not hide focusable / interactive elements.As such when you focus the top panel in a screen reader it still reads out the two buttons (on number 2). You can fix this by adding
tabindex="-1"
to the buttons when the front section is closed as this removes the items from the focus order (or just usedisplay: none
on the buttons until such time as the front cover is moved slightly, which would be preferable).One last minor point, visible text rather than a title would be great on the buttons for people who can't associate abstract iconography with actions.
How to fix it?
One suggestion on how to fix these issues would be to restructure the HTML so it looks more like an accordion (in the HTML I mean) and then use absolute positioning or a similar technique to position the draggable top section on top of the button holder.
So your button (as you could now make the front card an actual
<button>
) would open the hidden section which is a sibling of the button.It might take a little bit of JS trickery to handle touch start and (if you think a mouse slide action is better than a click) the mouse position movement but for screen readers and keyboard only users the behaviour would be much better.
Sorry I can't find any quick fixes with this one 😞 but the nested structure is where the issue lies.
Despite all of that I love the design, and obviously it is still very much worthy of a ❤ and a 🦄! 😁
p.s. I gave an answer on Stack Overflow that you could use the principle from that was about a button on top of another button where they appeared to be nested. Not sure if it is useful or not but I thought I would include it just in case.
answer re: Accessible nested button inside button?
Preword
Don't nest interactive elements
There is a reason that it isn't valid HTML to nest buttons or hyperlinks, it causes nightmares for knowing which action should be performed on a click (for a start) and for assistive technology this makes things even worse as it can confuse the accessibility…
I had hoped you'd leave a comment, thank you!
I've update my structure slightly and now the screen reader is reading out the collapsed state correctly. 😁
I only got a chance to view on my phone so couldn't fully test accessibility, I'm no expert either but have worked with accessibility agencies on some projects.
Just some quick notes, you don't want the title attribute and the aria-label at the same time, this can confuse some screen readers. Also I wouldn't suggest an h4 tag, assuming this is to be used on page along with other components it's unlikely that it would line up with an h4, if these arent to be headings I would consider a different tag, comes to mind but maybe there is a better choice yet
Glad to see you taking the initiative to test your skills while making accessibility a focus, not many bother to do much here so great job!
I've updated the h4 to another styled span. I can't see anything that says not to use a title though, it appears most screen readers ignore titles by default, and others don't support it at all.
Not many people hope I come along and write a massive list of things to tackle 😜🤣
At a glance on my phone it looks way better, if I get chance tomorrow I will have a look on PC!
If you get the pattern right I’m stealing it as I like the design so it isn’t anything other than selfishness 😉
This is UX so bad it is worthy of being included in one of Google's web apps.
Oh, what's wrong with the UX?
That's only the UX, I didn't take a close look at the code, but a quick look reveals the implementation uses events to change classes/css, which is really bad.
Ok, I'm not sure I agree with any of those points to be honest 😅
Though I do want to ask about why you say using "events to change classes/css" is "really bad"? It's the first time I've heard someone say this is bad.
How? If you have to make excuses for your libraries in your UI, you picked the wrong library.
That doesn't show on my screen.
Your example requires a double click.
They don't have any meaning to a user that is using the card nor the developer selecting which colors they should be. Actually it's worse, the color and display is almost 100% the toast display from bootstrap which indicates the "level" of the message.
That's irrelevant and doesn't make it good UI.
Cards are frequently discouraged in most frameworks, UX designs, so requiring their use is frequently problematic.
Writing JS to do something that css can do natively is an indication of lack of css knowledge. It's also way slower and requires management of unnecessary JS resources which provides another area where it's easy to get it wrong.
Hope that helps.
Which browser are you using? The cursor and drag work fine for me on mobile and on desktop in chrome, Firefox and edge?
What am I doing in JS that CSS can do natively?