# Floating chat buttons that cover part of keyboard focus

Source: https://easeweb.dev/learn/focus-not-covered
Topics: Keyboard and focus > No part of the focused control is covered (WCAG 2.4.12)
The fix: Keep floating elements off the area the browser scrolls focus into. Either reserve the launcher's corner with scroll-padding-bottom, and pad the end of the content so the last row can scroll above it, or take the launcher out of the floating layer and put it in the page's header.
Test with: keyboard, zoom
References: https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-enhanced.html https://www.w3.org/WAI/WCAG22/Techniques/css/C43 https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A community centre lists its workshops: "Bread baking basics", "Intro to watercolour", "Repairing a bicycle" and more. Each row ends in a "Book" button on the right. A round "Chat with us" button floats in the bottom-right corner of the window, over the page. Press Tab down the list: as focus reaches the rows near the bottom, the launcher sits over the right half of their "Book" buttons. Focus is there and part of its ring shows, but the button's label and most of its outline are behind the launcher. Scroll a little and press Shift + Tab, and the same happens on the way back up.

## Why it fails

When focus moves, the browser scrolls only when the element is outside the scroll area. A button under a floating launcher is inside it, so the browser treats it as visible and leaves the page where it is. Nothing tells the browser that the bottom-right corner is covered. Success criterion 2.4.12, at level AAA, requires that when a control receives keyboard focus no part of it is hidden by content the author put there. The level AA criterion, 2.4.11, asks only that it is not entirely hidden, so this button passes 2.4.11 and fails 2.4.12. The Understanding page adds that translucent overlays count too: a faded or blurred layer over the focused control fails, even though the control shows through.

## Who is affected

Anyone tabbing through the page while the launcher shows. On a phone-width layout the launcher covers a larger share of each row, so more controls end up under it.

## What automation and AI miss

Each button has a name, a focus style and a place in the tab order, and an automated check that loads the page sees a visible button. The covering only happens after scrolling, at some window sizes, and the launcher is often added by a third-party script after load. Most scanners do not tab through a scrolled page, and those that do usually report only fully hidden focus. A generated layout often adds a floating chat button with `position: fixed` and nothing that reserves its corner.

## Before: a floating launcher with nothing reserving its space

```css
.chat-launcher {
  position: fixed;
  inset-inline-end: 1rem;
  bottom: 1rem;
  inline-size: 3.5rem;
  block-size: 3.5rem;
  border-radius: 50%;
}
```

The launcher covers the bottom 4.5rem of the window's right side, and the page scrolls under it. Any button the browser considers already in view can stop there, half hidden, and the last row on the page can never scroll above it.

## After: scroll-padding the launcher's corner

```css
:root {
  --launcher-size: 3.5rem;
  --launcher-inset: 1rem;
  /* The launcher, its offset from the edge, and a small gap above it. */
  --launcher-reserve: calc(var(--launcher-size) + var(--launcher-inset) + 0.5rem);
}
.chat-launcher {
  position: fixed;
  inset-inline-end: var(--launcher-inset);
  bottom: var(--launcher-inset);
  inline-size: var(--launcher-size);
  block-size: var(--launcher-size);
  border-radius: 50%;
}
html {
  scroll-padding-bottom: var(--launcher-reserve);
}
/* Let the last row scroll above the launcher. */
main {
  padding-bottom: var(--launcher-reserve);
}
```

`scroll-padding-bottom` tells the browser that the bottom strip of the window is not really visible, so a control that receives focus there is scrolled up until it clears the launcher. The padding at the end of `main` gives the page enough height to do that for the last row. Both come from the same tokens as the launcher, so they stay in step. This holds only while the launcher keeps that size: if it grows into a wider "Chat with us" pill, or opens into a panel, the reserve must grow with it, and if a cookie banner shows below it, the two heights add up. The reserve takes a full-width strip, so it also costs some room on short windows.

## After: the launcher in the header

```html
<header class="site-header">
  <a href="/">Community Centre</a>
  <button type="button" class="chat-launcher">Chat with us</button>
</header>
```

```css
.chat-launcher {
  position: static;
}
```

The launcher is an ordinary button in the header, part of the page rather than a layer over it, so nothing floats over the content and there is no space to keep in step. It also comes early in the tab order, where keyboard users find it. It gives up the always-visible corner, so people scrolled far down reach it by going back to the top or through a link in the footer; if the header is sticky, the sticky header needs its own space reserved, as in [the guide on sticky headers](/learn/focus-not-obscured).

## Which option to use

Both pass. `scroll-padding` keeps the floating launcher the design asked for and is a few lines of CSS, but it depends on the reserve matching the launcher in every state, and a third-party widget can change its size without telling you. Moving the launcher into the header removes the overlap for good, with nothing to measure, but it changes the design and takes the launcher out of view as people scroll. Choose `scroll-padding` when the floating corner is a firm requirement and you control the launcher's size; choose the header when you can change the template or the widget's size is outside your control.

## Implementation decisions

Count everything that floats over the page, because each needs its space reserved or a place in the layout:

- a chat launcher, which often grows into a wider pill or opens into a panel;
- a "back to top" button, which usually appears only after scrolling;
- a cookie or promotion banner, whose height adds to the launcher's when both show;
- a sticky header, which covers the top of the window on the way back up.

A soft fade or gradient at the bottom of the window, used to hint at more content, counts as covering under 2.4.12 even though the control shows through it; reserve its height as well or remove it. Shadows around a launcher are part of it, so include them in the gap.

The criterion is about the focused control, not its focus ring. Even so, leave enough gap that the ring's offset clears the launcher too, or the ring may be the part people cannot see.

Third-party chat widgets usually inject their own fixed element. Check whether the vendor lets you set its offset or hide it, set the reserve from the size the widget actually renders, and recheck after vendor updates. If the widget opens a non-modal panel over the page, the panel covers far more; reserve its height while it is open, or move focus into it and return it on close.

## Verify the fix

1. Scroll to the top, then press Tab through every control to the end of the page with the launcher showing. Each focused control is completely visible, with nothing on top of it, not even a translucent layer.
2. From the end, press Shift + Tab back to the top and check the same thing.
3. Tab to the last control on the page and check that it can sit fully above the launcher.
4. Repeat at 200% zoom and in a 320-pixel-wide window.
5. Repeat with each overlay state you ship: the launcher closed and open, the cookie banner shown and answered, the back-to-top button shown.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the launcher, banners or page layout change.

## Limits

This is a starting pattern, not a guarantee for every application. Level AAA criteria are not required for whole sites, and some content cannot meet them, but this one is usually cheap to meet. Browsers apply `scroll-padding` to focus scrolling in their current versions, but check yours, and check scroll containers inside the page, which each need their own padding. Token names in the snippets are placeholders for your own design tokens.
