Включить: фокус только на использовании клавиатуры (или нажатие вкладки)
Я хочу отключить :focus
когда это не нужно, потому что мне не нравится, как выглядит моя навигация, когда на ней фокусируется. Он использует тот же стиль, что и .active
и это сбивает с толку. Однако я не хочу избавляться от этого для людей, которые используют клавиатуру.
Я думал добавить класс enabled-focus
на теле на вкладке нажмите, а затем иметь body.enabled-focus a:focus{...}
но это добавило бы много дополнительного CSS для каждого элемента, который имеет фокус. Затем удалите этот класс из тела первой мышкой вниз.
Как бы я пошел по этому поводу? Есть ли лучшее решение?
11 ответов
Эта превосходная статья Романа Комарова представляет собой жизнеспособное решение для создания стилей фокуса только для клавиатуры для кнопок, ссылок и других элементов контейнера, таких как span или div (которые искусственно сделаны фокусируемыми с помощью атрибута tabindex)
Решение:
button {
-moz-appearance: none;
-webkit-appearance: none;
background: none;
border: none;
outline: none;
font-size: inherit;
}
.btn {
all: initial;
margin: 1em;
display: inline-block;
}
.btn__content {
background: orange;
padding: 1em;
cursor: pointer;
display: inline-block;
}
/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
position: relative;
}
/* All the states on the inner element */
.btn:hover > .btn__content {
background: salmon;
}
.btn:active > .btn__content {
background: darkorange;
}
.btn:focus > .btn__content {
box-shadow: 0 0 2px 2px #51a7e8;
color: lime;
}
/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
outline: none;
}
<h2>Keyboard-only focus styles</h2>
<button id="btn" class="btn" type="button">
<span class="btn__content" tabindex="-1">
I'm a button!
</span>
</button>
<a class="btn" href="#x">
<span class="btn__content" tabindex="-1">
I'm a link!
</span>
</a>
<span class="btn" tabindex="0">
<span class="btn__content" tabindex="-1">
I'm a span!
</span>
</span>
<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing - behold - focus styles</p>
Codepen
1) Оберните содержимое исходного интерактивного элемента внутри дополнительного внутреннего элемента с помощью tabindex="-1"
(см. объяснение ниже)
Поэтому вместо того, чтобы сказать:
<button id="btn" class="btn" type="button">I'm a button!</button>
сделай это:
<button id="btn" class="btn" type="button">
<span class="btn__content" tabindex="-1">
I'm a button!
</span>
</button>
2) Переместите стиль CSS во внутренний элемент (макет CSS должен остаться на исходном внешнем элементе) - чтобы ширина / высота внешнего элемента исходила от внутреннего и т. Д.
3) Удалите стиль фокуса по умолчанию из внешних и внутренних элементов:
.btn:focus,
.btn__content:focus {
outline: none;
}
4) Добавьте стиль фокуса обратно к внутреннему элементу, только когда внешний элемент имеет фокус:
.btn:focus > .btn__content {
box-shadow: 0 0 2px 2px #51a7e8; /* keyboard-only focus styles */
color: lime; /* keyboard-only focus styles */
}
Почему это работает?
Хитрость здесь в том, чтобы установить внутренний элемент с tabindex="-1"
- см. MDN:
Отрицательное значение (обычно tabindex="-1" означает, что элемент должен быть фокусируемым, но не должен быть доступен с помощью последовательной навигации по клавиатуре...
Таким образом, элемент можно фокусировать с помощью щелчков мыши или программно, но с другой стороны - он не может быть достигнут с помощью "вкладок" клавиатуры.
Поэтому, когда по интерактивному элементу нажимают - внутренний элемент получает фокус. Стили фокуса не будут показаны, потому что мы их удалили.
.btn:focus,
.btn__content:focus {
outline: none;
}
Обратите внимание, что только один элемент DOM может быть сфокусирован в данный момент времени (и document.activeElement
возвращает этот элемент) - поэтому будет сфокусирован только внутренний элемент.
С другой стороны: когда мы вкладываем с помощью клавиатуры - только внешний элемент получит фокус (помните: внутренний элемент имеет tabindex="-1" и недоступен при последовательной навигации по клавиатуре) [Обратите внимание, что для фокусируемые внешние элементы, такие как интерактивные <div>
- мы должны искусственно сделать их фокусируемыми, добавив tabindex="0"
]
Теперь наш CSS включается и добавляет стили фокуса только для клавиатуры the inner element
,
.btn:focus > .btn__content {
box-shadow: 0 0 2px 2px #51a7e8; /* keyboard-only focus styles */
color: lime; /* keyboard-only focus styles */
}
Конечно, мы хотим убедиться, что когда мы нажимаем enter
- мы не сломали наш интерактивный элемент, и JavaScript будет работать.
Вот демонстрация, демонстрирующая, что это действительно так, но учтите, что вы получаете это бесплатно (т. Е. Нажимаете Enter, чтобы вызвать событие щелчка) для по сути интерактивных элементов, таких как кнопки и ссылки... для других элементов, таких как пролеты, - вам нужно закодировать это вручную:)
//var elem = Array.prototype.slice.call(document.querySelectorAll('.btn'));
var btns = document.querySelectorAll('.btn');
var fakeBtns = document.querySelectorAll('.btn[tabindex="0"]');
var animate = function() {
console.log('clicked!');
}
var kbAnimate = function(e) {
console.log('clicking fake btn with keyboard tab + enter...');
var code = e.which;
// 13 = Return, 32 = Space
if (code === 13) {
this.click();
}
}
Array.from(btns).forEach(function(element) {
element.addEventListener('click', animate);
});
Array.from(fakeBtns).forEach(function(element) {
element.addEventListener('keydown', kbAnimate);
});
button {
-moz-appearance: none;
-webkit-appearance: none;
background: none;
border: none;
outline: none;
font-size: inherit;
}
.btn {
all: initial;
margin: 1em;
display: inline-block;
}
.btn__content {
background: orange;
padding: 1em;
cursor: pointer;
display: inline-block;
}
/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
position: relative;
}
/* All the states on the inner element */
.btn:hover > .btn__content {
background: salmon;
}
.btn:active > .btn__content {
background: darkorange;
}
.btn:focus > .btn__content {
box-shadow: 0 0 2px 2px #51a7e8;
color: lime;
}
/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
outline: none;
}
<h2>Keyboard-only focus styles</h2>
<button id="btn" class="btn" type="button">
<span class="btn__content" tabindex="-1">
I'm a button!
</span>
</button>
<a class="btn" href="#x">
<span class="btn__content" tabindex="-1">
I'm a link!
</span>
</a>
<span class="btn" tabindex="0">
<span class="btn__content" tabindex="-1">
I'm a span!
</span>
</span>
<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing + enter - behold - our interactive elements work</p>
Codepen
NB:
1) Хотя это кажется чрезмерно сложным решением, для решения, не поддерживающего JavaScript, оно на самом деле весьма впечатляет. Более простые "решения" только для CSS :hover
а также :active
стиль псевдо-класса просто не работает. (если, конечно, вы не предполагаете, что интерактивный элемент сразу исчезает при нажатии, как кнопка в модальном слове)
button {
-moz-appearance: none;
-webkit-appearance: none;
background: none;
border: none;
font-size: inherit;
}
.btn {
margin: 1em;
display: inline-block;
background: orange;
padding: 1em;
cursor: pointer;
}
.btn:hover, .btn:active {
outline: none;
}
<h2>Remove css :focus outline only on :hover and :active states</h2>
<button class="btn" type="button">I'm a button!</button>
<a class="btn" href="#x">I'm a link!</a>
<span class="btn" tabindex="0">I'm a span!</span>
<h3>Problem: Click on an interactive element.As soon as you hover out - you get the focus styling back - because it is still focused (at least regarding the button and focusable span) </h3>
Codepen
2) Это решение не идеально: Firefox на окнах по-прежнему будет использовать стили фокуса для кнопок при нажатии - но это похоже на ошибку Firefox (см. Статью)
3) Когда браузеры реализуют псевдокласс :focus-ring - решение этой проблемы может быть намного проще - (см. Статью). Для чего стоит, есть polyfill для :focus-ring
- см. эту статью Крис ДеМарс
Прагматичная альтернатива стилям фокусировки только на клавиатуре
Так что добиться стиля фокусировки только с клавиатуры на удивление сложно. Один альтернативный / обходной путь, который намного проще и может не только удовлетворить ожидания дизайнера, но и быть доступным, - это стилизовать фокус так же, как стиль ховер.
Codepen
Таким образом, хотя технически это не реализация стилей только для клавиатуры, это по существу устраняет необходимость в стилях только для клавиатуры.
Пример из практики: страница входа в Facebook
Facebook использует чуть-чуть Javascript на своей странице входа в систему (июнь 2018 года).
Javascript определяет, когда пользователь щелкнул мышью или использовал клавиатуру, и включает и выключает класс на теле: <body class="using-mouse">
Тогда правила CSS могут использовать этот класс, чтобы показать или скрыть соответствующий стиль фокуса на соответствующих элементах.
Вот пример кода (также доступен на CodePen):
// Let the document know when the mouse is being used
document.body.addEventListener('mousedown', function() {
document.body.classList.add('using-mouse');
});
document.body.addEventListener('keydown', function() {
document.body.classList.remove('using-mouse');
});
/* The default outline styling, for greatest accessibility. */
/* You can skip this to just use the browser's defaults. */
:focus {
outline: #08f auto 2px;
}
/* When mouse is detected, ALL focused elements have outline removed. */
/* You could apply this selector only to buttons, if you wanted. */
body.using-mouse :focus {
outline: none;
}
<input>
<button>Submit</button>
Пример: страница входа в GMail
В качестве альтернативы, GMail просто стилизует сфокусированные кнопки с более тяжелой тенью, чем не сфокусированные кнопки, независимо от того, находится ли пользователь на мыши или клавиатуре.
Это согласованно, проще для реализации и тестирования и не требует Javascript.
Но это компромисс. Он передает информацию о фокусе, в которой пользователи мыши на самом деле не заинтересованы, и, возможно, он слишком тонкий для пользователей клавиатуры.
Тем не менее, этот компромисс гораздо лучше, чем одна из крайностей (сильный контур везде или вообще никакого плана).
Удаление outline
ужасно для доступности! В идеале кольцо фокусировки появляется только тогда, когда пользователь намеревается использовать клавиатуру.
2018 Ответ: Использование : видимый фокус. В настоящее время это предложение W3C для стилизации фокуса только с клавиатуры с использованием CSS. Пока основные браузеры не поддерживают его, вы можете использовать этот надежный полифилл. Не требует добавления дополнительных элементов или изменения tabindex
,
/* Remove outline for non-keyboard :focus */
*:focus:not(.focus-visible) {
outline: none;
}
/* Optional: Customize .focus-visible */
.focus-visible {
outline-color: lightgreen;
}
Я также написал более подробный пост на тот случай, если вам нужно больше информации.
С этой проблемой вы наверняка столкнетесь. Преимущество таких проблем в том, что если вы однажды найдете решение, оно больше не будет вас беспокоить.
Наиболее элегантное решение кажется самым простым: не удаляйте контур:focus, делайте это на:active вместо этого - в конце концов,:active - это динамический псевдокласс, который явно работает со стилями, которые должны применяться при фокусируемый элемент щелкается или иным образом активируется.
a:hover, a:active { outline: none; }
Единственные незначительные проблемы с этим методом: если пользователь активирует ссылку, а затем использует кнопку "Назад" браузера, контур становится видимым. Да, и старые версии Internet Explorer, как известно, путаются с точным значением:focus,:hover и:active, поэтому этот метод не работает в IE6 и ниже.
Типп
Существует тривиальный обходной путь, предотвращающий "выплескивание" контуров путем добавления простого overflow:hidden
, который сохраняет контур вокруг кликабельной части самого элемента.
&:focus:not(:hover) { }
Это не будет работать в 100% случаев, но я думаю, что для большинства людей этого должно быть достаточно.
Это предотвратит :focus
состояние является триггером при щелчке, потому что мышь должна быть над элементом мыши, чтобы щелкнуть по нему.
До :focus-visible
отсутствует во всех популярных вечнозеленых браузерах, вы можете использовать этот простой трюк в глобальной части вашего CSS без каких-либо полифилов:
@media (pointer: coarse) {
*:focus {
outline: none;
}
}
а затем добавьте эффекты фокусировки, как обычно, с :focus
.
В этот момент вы, вероятно, узнали, что установка outline: none;
использовать элементы с фокусом по умолчанию - ужасная идея с точки зрения доступности. Это, конечно, правда.
Однако, если вы включите это правило в pointer: coarse
медиа-запрос, он становится очень полезным, так как он будет применяться только к мобильным телефонам и планшетам, но не к настольным компьютерам. Это именно то, чего вы хотите достичь.
Единственная проблема, о которой я могу думать, - это мобильные пользователи с клавиатурами, которые они используют для перехода по контенту, но я не уверен, много ли таких пользователей. Итак, в конечном итоге:focus-visible
будет лучшим решением, но пока этого должно быть достаточно.
Как заявляли некоторые, это путь с точки зрения чистого решения CSS. Я хочу предоставить самый простой способ решить эту проблему со стилем, используя только CSS, однако он имеет некоторые недостатки в поддержке браузера и полезен не для всех:
@supports not selector(:focus-visible) {
:focus {
// Fallback styles for browsers that doesn't support :focus-visible.
}
}
:focus-visible {
// Tab focus styles
}
Применять только
:focus
в случае, если это действительно необходимо для предотвращения вмешательства со стилями.
:focus-visible
будет просто игнорироваться браузерами, не поддерживающими его, и поэтому не требует
@supports
или такой.
Узнайте больше о поддержке браузерами селектора @supports и :focus-visible .
Если вам случится использовать CSS в JS, как я, вы можете использовать CSS.supports() для условного рендеринга резервного CSS на случай, если вам нужно заботиться о браузерах, которые не поддерживают
@supports selector
. Что-то в духе
if (CSS.supports("selector(:focus-visible)")) {...}
.
Играя с принятым решением Danield, я нашел альтернативный, более простой способ, основанный на концепции внутреннего / внешнего div.
1) Создайте внешний и внутренний элемент. Дайте внешнему элементу tabindex="0" и внутреннему элементу tabindex = "- 1"
<div role="button" class="outer" tabindex="0">
<span class="inner" tabindex="-1">
I'm a button!
</span>
</div>
2) В css удалите контур из внутреннего элемента, когда он сфокусирован:
.inner:focus{
outline: none;
}
3) Примените любые обработчики событий мыши или щелчка к внутреннему элементу. Примените любые события фокуса (onfocus, onblur, onkeydown) к внешнему элементу.
Например:
<div role="button" class="outer" tabindex="0" onfocus="focusEventHandler()" onkeydown="handleKeyDown.bind(this, myEventHandler)">
<div class="inner" tabindex="-1" onClick="myEventHandler()">
I'm a button!
</div>
</div>
** Сохраняйте размер и расположение таким образом, чтобы внутренний элемент полностью перекрывал внешний элемент. Поместите всю "кнопку" со стилем на внешний элемент.
Как это работает:
Когда пользователь нажимает "кнопку", он нажимает на внутренний элемент, у которого удален контур фокуса. Невозможно нажать на внешний элемент, поскольку он закрыт внутренним элементом. Когда пользователь использует клавиатуру для перехода к "кнопке", он попадает во внешний элемент (tabindex="0" делает элемент доступным с помощью "tab"), который получает контур фокуса, но внутренний элемент не доступен через tab (с tabindex="-1") и не получает контур фокуса при нажатии.
Как упоминалось другими, существует
:focus-visible
вариант с неплохой поддержкой браузера https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible .
Я нашел эту статью очень полезной и хотел ею поделиться https://css-tricks.com/keyboard-only-focus-styles/
Пример в SCSS:
button {
&:focus-visible {
border: 2px solid #004EA3;
}
}
Там нет четкого решения. Я сделал одно решение Hackish: примените событие click к вашему главному контейнеру и напишите код ниже
_handleMouseClick = (event) => {
if(event.detail){
document.activeElement.blur();
}
}
Когда вы щелкаете мышью, вы получаете event.detail = 1, при этом щелчок размывает этот элемент, так что он удаляет контур, а при щелчке клавиатуры мы получаем event.detail = 0, поэтому в случае клавиатуры ведите себя нормально
ИЛИ ЖЕ
В css файле
body.disableOutline *:focus{
outline: none !important;
}
В Main JS
document.addEventListener('click', _handleMouseClick,true);
document.addEventListener('keydown',_keydown,true);
function _handleMouseClick(event){
if(event.detail){
document.getElementsByTagName("body")[0].classList.add("disableOutline");
}
}
function _keydown(e){
document.getElementsByTagName("body")[0].classList.remove("disableOutline");
}
В библиотеке blueprintjs есть хорошее решение для этого.
https://blueprintjs.com/docs/#core/accessibility
Однако я еще не мог понять, как им это удалось.