akcasoy
akcasoy

Reputation: 7215

How to have a css outline on a <button> tag, only by tab, but not on click?

We have some buttons, which are styled with css and have some icons like this:

enter image description here

This button has as default outline property, so that everytime we click on it, it has an outline (in chrome blue):

enter image description here

to get rid of it, we can of course overwrite this property like:

outline: none

but then when we tab through and reach this button, it will also not have any outline, which is a bad practice for accesibility.

Can we achive this so that this outline appears only when we come on this button with tab, but not when we click on it?

Just as info: We have also some a tags which seems visually just like that, and with a tags we have exact this behaviour we want, outlines appear only when we tab on to that link, but not on click. We just want to have exact same behaviour with button tags also.

Upvotes: 16

Views: 9558

Answers (6)

endlacer
endlacer

Reputation: 175

I found an easy way, that is pure html/css and does not use any javascript.

  1. Wrap the content of the button inside an additional outer element (e.g. div) and make the wrapper focusable/tabable with tabindex="0"

  2. The inner element gets tabindex="-1"

So instead of:

<button>Buttontext</button>

do this:

<div tabindex="0">
    <button tabindex="-1">
        Buttontext
    </button>
</div>

In that way you can tab onto the outer div and the browser draws its focus-outline. When you click into the button, you select the inner element, which has no outline (outline: none)

Upvotes: 0

kimbaudi
kimbaudi

Reputation: 15545

Can we achive this so that this outline appears only when we come on this button with tab, but not when we click on it?

Yes you can by using :focus-visible.

If you want an outline to appear when button is tabbed and not clicked, set outline: none on the button on :focus but not on :focus-visible:

button:focus:not(:focus-visible) {
  outline: none;
}

Upvotes: 16

akcasoy
akcasoy

Reputation: 7215

Since none of the answers above really worked for me in all cases (most of them ignores at least the fact that one can again switch to mouse, and the boxshow must then also be removed from the button. It does not have to be removed always from the eventlistener), here is the answer for me:

If your html tags are more or less on the same level (so you don't have to create a listener for the whole page):

  • The previous element, whose 'tab' should switch focus to the button should have a keydown/keyup event which then runs:

    if ($event.keyCode === '9' && !$event.shiftKey) {
      $event.target.nextSibling.style.boxShadow = '0 0 5px 1px #305C73';
    }
    
  • When you also want to support a shift+tab (back tabbing), which you should, you also need this for the next element:

    if ($event.keyCode === '9' && $event.shiftKey) {
      $event.target.previousSibling.style.boxShadow = '0 0 5px 1px #305C73';
    }
    
  • The button itself show remove it by its own blur event:

    $event.target.style.boxShadow = 'none';
    
  • The button itself can still set its outline to none:

    outline: none
    

Since i asked the question independent of the JS-technology in background, the event handling parts are not included here. But in case of an angular app, one can use sth. like this:

<previousTag (keydown)="addBoxShadowBorButtonOnTab($event)">

<button (blur)="removeBowShadow($event)">

<nextTag (keydown)="addBoxShadowBorButtonOnShiftTab($event)">

In plain javascript this part is done like in the answers above with an EventListener.

If your tags are within other tags and you have a hierarchical DOM structure, or you cannot change your 'previous' and 'next' tags:

In my case we had sth. like this:

<li>
  <previousTag>
  <button>
</li>
<li>
  <nextTag>
<li>

It is better to register a listener for keyup/keydown events for adding your boxShadow since you are then more independent of your HTML Structure and future DOM changes. So you can set your outline independent of from which elements you are coming from.

In my Angular app, it seems sth. like this:

  @HostListener('document:keyup.shift.tab', ['$event'])
  @HostListener('document:keyup.tab', ['$event']) onTabHandler(event: KeyboardEvent) {
    const focusedElm = event.target;
    if (focusedElm.id === 'my-heart-button') {
      focusedElm.style.boxShadow = '0 0 5px 1px #305C73';
    }
  }

Removing the outline should still work exactly the same way like above.

This is the only way to ensure that the Buttons will always have an outline 'just during focusing on while tabbing' for accessibility, but they will not appear on a mouse click concerning aesthetics.

Upvotes: 1

Luk&#225;š Gibo Vaic
Luk&#225;š Gibo Vaic

Reputation: 4420

When you click on an element it casts :active on it, so you want to chain :active and :focus together:

button:active:focus {
  outline: none;
}
<button>Button</button>

As you said this doesnt work with additional css, in this case you have to implement a bit complicated solution, where you add class to body when user uses tab for a first time, otherwise you remove outlines all together

function handleFirstTab(e) {
    if (e.keyCode === 9) { // the "I am a keyboard user" key
        document.body.classList.add('user-is-tabbing');
        window.removeEventListener('keydown', handleFirstTab);
    }
}

window.addEventListener('keydown', handleFirstTab);
body:not(.user-is-tabbing) button:focus {
  outline: none;
}

button {
    background-color: red;
}
<button>Button</button>

Upvotes: 12

לבני מלכה
לבני מלכה

Reputation: 16251

Use .btn:focus{ outline:0; } to remove outline

And use addEventListener to target Tab click and set box-shadow if tab on your element

document.addEventListener('keydown', function(e) {
var elem=document.getElementById('heart');
  if (e.key === 'Tab' &&  document.activeElement === elem) {
     elem.style.boxShadow="0 0 0 0.2rem rgba(0,123,255,.25)";
  }
else{
elem.style.boxShadow="none";
}
  
});
.btn {
  width: 80px;
  height: 80px;
  transform: rotate(-46deg);
  border: none;
  background: url(https://image.flaticon.com/icons/svg/579/579268.svg);
}
.btn:focus{
outline:0;
}
<button class="btn" id="heart">
</button>

You can target tab click by css with this plugin:https://github.com/ten1seven/track-focus

body[data-whatinput="keyboard"] .btn:focus {
  box-shadow:  0 0 0 0.2rem rgba(0,123,255,.25);
}

Upvotes: 1

wscourge
wscourge

Reputation: 11281

If your icons are some kind of fonts, your best option is using text-shadow instead:

button {
  outline: none!important;
  background: transparent;
  border-color: transparent;
  color: red;
  font-size: 30px;
}

button:focus i.fa {
  text-shadow: 1px 1px 5px rgba(255, 0, 0, 0.7);
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">


<button class="btn">
  <i class="fa fa-heart-o"></i>
</button> 

In this example, I used font-awesome-4.7 and generated the text-shadow with text-shadow generator.

Upvotes: 1

Related Questions