Most teams build their website feedback button twice. The first version is a styled div dropped into the bottom right corner on a Friday afternoon. The second version arrives after someone tabs through the page with a keyboard, or opens the site on a phone, and finds the button parked on top of the checkout total.
A website feedback button is an on-page control, usually a small fixed button in a corner or a tab pinned to one edge, that opens a form or panel where a visitor can send a comment, a bug report or a feature request without leaving the page. It is markup in your site. It is not a physical kiosk button, not a hardware terminal in an airport, and not a smiley-face panel screwed to a wall.

This post gives you the markup, the CSS, the placement decision and the accessibility floor. Copy what you need.
The HTML and CSS for a website feedback button
Start from a real button element with visible text inside it. The WAI-ARIA Authoring Practices button pattern specifies that when the button has focus, Space activates it and Enter activates it, and that the accessible name comes from the element’s text content. A native button gives you both for free. A div with a click handler gives you neither until you write them yourself.
<button
type="button"
class="feedback-button"
aria-haspopup="dialog"
aria-expanded="false"
aria-controls="feedback-panel"
>
<svg class="feedback-button__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path
d="M4 5h16v11H8l-4 4V5z"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linejoin="round"
/>
</svg>
<span class="feedback-button__label">Feedback</span>
</button>
Three details in that snippet do real work. aria-hidden="true" on the SVG stops a screen reader announcing a decorative shape. aria-controls points at the panel the button opens. aria-expanded starts at false and your script flips it, so assistive technology knows whether the panel is currently open.
Now the CSS. This is the bottom-corner version, which is the one most sites want:
.feedback-button {
position: fixed;
right: 1rem;
bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
z-index: 60;
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-height: 44px;
min-width: 44px;
padding: 0.625rem 1rem;
font: inherit;
font-weight: 600;
color: #fff;
background: var(--feedback-button-bg, #0f766e);
border: 0;
border-radius: 999px;
box-shadow: 0 6px 20px rgb(0 0 0 / 0.18);
cursor: pointer;
transition: transform 150ms ease, box-shadow 150ms ease;
}
.feedback-button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 28px rgb(0 0 0 / 0.22);
}
.feedback-button:focus-visible {
outline: 3px solid currentColor;
outline-offset: 3px;
}
.feedback-button__icon {
width: 20px;
height: 20px;
}
@media (prefers-reduced-motion: reduce) {
.feedback-button,
.feedback-button:hover {
transition: none;
transform: none;
}
}
And the script that keeps aria-expanded honest:
const button = document.querySelector(".feedback-button");
const panel = document.getElementById("feedback-panel");
button.addEventListener("click", () => {
const isOpen = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!isOpen));
panel.hidden = isOpen;
if (!isOpen) panel.querySelector("textarea, input, button")?.focus();
});
That is the whole component. No framework, no dependency, no icon library.
What makes a website feedback button accessible?
An accessible website feedback button is a native button element with a visible text label, a target of at least 24 by 24 CSS pixels, a focus style you can see, and a state attribute that tracks whether its panel is open. Four things, and three of them are one line of CSS or one attribute.
The size number is not a preference. WCAG 2.2 Success Criterion 2.5.8, Target Size (Minimum), is Level AA and requires that the target for pointer inputs is “at least 24 by 24 CSS pixels”, with exceptions for inline targets, spacing, equivalent controls and user-agent-controlled sizing. Success Criterion 2.5.5, Target Size (Enhanced), is Level AAA and asks for at least 44 by 44 CSS pixels. The CSS above sets min-height and min-width to 44px, which clears both.
The focus style is where most custom buttons fail. MDN’s page on :focus-visible explains why: browsers used to draw a focus ring on every focused element, many authors thought it was ugly and removed it, and MDN’s verdict is blunt. “Changing focus style can decrease usability, while removing focus styles makes keyboard navigation inaccessible for sighted users.” :focus-visible is the fix, because it lets the browser decide when the indicator helps, so a mouse click gets no ring and a Tab key press does. It has been available across browsers since March 2022.
| Requirement | What to write | Where it comes from |
|---|---|---|
| Keyboard activation | A native button element |
WAI-ARIA APG button pattern |
| Accessible name | Visible text inside the button | WAI-ARIA APG button pattern |
| Touch and pointer target | min-height: 44px; min-width: 44px |
WCAG 2.5.8 at 24px, 2.5.5 at 44px |
| Visible focus | :focus-visible with an outline |
MDN, CSS selectors |
| Open or closed state | aria-expanded on the button |
ARIA, flipped by your script |
| Motion preference | @media (prefers-reduced-motion: reduce) |
CSS media queries |
If you hide the text label on narrow screens, hide it with a clip-path utility rather than display: none. Text removed with display: none is removed from the accessible name too, and your button becomes an unlabelled icon.
Side tab or bottom corner: where should the button go?
Put the button in the bottom right corner if visitors use your site on phones, and use a side tab if the page has its own bottom-anchored UI such as a cookie banner, a sticky add-to-cart bar or a chat launcher. Those two cases cover almost every site, and the deciding factor is what else already lives at the bottom of the viewport.

| Placement | Best for | Strength | Limitation |
|---|---|---|---|
| Bottom right corner | Marketing sites, docs, phone-heavy traffic | Closest to where a thumb rests, familiar from chat widgets | Competes with cookie banners, chat and sticky bars |
| Bottom left corner | Sites that already have chat on the right | Keeps two launchers from stacking | Less conventional, users hunt for it |
| Fixed side tab, right edge | Dashboards and apps with a busy bottom bar | Stays clear of bottom chrome entirely | Rotated text is hard to read, poor on narrow screens |
| Inline, in the footer or header nav | Content sites that do not want fixed chrome | Never covers anything | Far lower discovery, only found by people already looking |
The side tab is a four-line change to the CSS above:
.feedback-button--tab {
right: 0;
top: 50%;
bottom: auto;
translate: 0 -50%;
writing-mode: vertical-rl;
padding: 1rem 0.625rem;
border-radius: 8px 0 0 8px;
}
writing-mode: vertical-rl turns the label sideways without a CSS rotation, so the button’s hit area stays exactly where it looks like it is. A rotated element keeps its original box for layout purposes, which is how side tabs end up with a clickable region floating somewhere else on the page.
One rule holds for both: switch a side tab back to the corner below roughly 640px. A vertical tab on a phone eats a strip of screen height that the reader needs, and there is rarely a sticky bottom bar on mobile worth dodging.
What does a fixed feedback button do on mobile?
On mobile a fixed feedback button has to survive the home indicator, the collapsing browser toolbar and a thumb that is less precise than a mouse pointer, so it needs a safe-area offset, a generous target and no dependence on hover. The env(safe-area-inset-bottom, 0px) in the CSS above handles the first of those.
MDN documents the safe-area-inset-* variables under env() as “the safe distance from the top, right, bottom, or left inset edge of the viewport, defining where it is safe to place content into without risking it being cut off by the shape of a non-rectangular display”, and notes that the four values are 0 on a plain rectangular viewport and a positive pixel value otherwise. So the calc() costs you nothing on a desktop monitor and rescues the button from the home indicator on a phone. The variable only reports a non-zero value when the page opts in with viewport-fit=cover in its viewport meta tag.
Two more mobile habits worth keeping:
- Use
dvhrather thanvhfor anything sized against the viewport near that button.vhis frozen against the largest viewport, so a panel sized invhhides its own bottom edge behind the browser toolbar. - Never put the only affordance behind
:hover. A tooltip that explains what the button does has to be readable without a pointer, which usually means putting the word “Feedback” on the button itself.
If your visitors move the button around themselves, the draggable Sleekplan feedback button walkthrough has the pointer handling, though it is worth saying that a draggable launcher is a workaround for a placement decision you have not made yet.
The WCAG rule most feedback buttons break
The rule that catches fixed feedback buttons is WCAG 2.2 Success Criterion 2.4.11, Focus Not Obscured (Minimum), a Level AA criterion that says a component receiving keyboard focus must not be entirely hidden by author-created content. A corner button rarely breaks it. A full-width sticky bar built from the same component breaks it constantly.
The W3C Understanding document for 2.4.11 names the offenders directly: “Typical types of content that can overlap focused items are sticky footers, sticky headers, and non-modal dialogs”, and it says that “a notification implemented as sticky content, such as a cookie banner, will fail this success criterion if it entirely obscures a component receiving focus”. The remedies it lists are making the overlay modal so the user has to dismiss it first, or “using scroll padding so the banner does not overlap other content”.
In practice that means one extra declaration whenever your feedback UI spans the width of the viewport:
:root {
/* Reserve the height of any bottom-fixed chrome so focused
elements scroll into view above it, not behind it. */
scroll-padding-bottom: 5rem;
}
Then tab through a long page with the button on screen. If focus lands on a link you cannot see, you have found the bug that no visual QA pass will ever catch.
When to stop maintaining the button yourself
Hand the button over to a hosted widget once you need the panel behind it to do more than post a form, because that panel is where the real work lives: authentication, voting, status changes, replies and email notifications. The button is an afternoon. The panel is a product.
Sleekplan’s embeddable feedback widget is one script tag that injects its own launcher, and the launcher position, the panel colour and the labels are all configurable.
If you would rather keep the button you just built, you can. Turn the default launcher off in Settings, then add a data attribute to any element on the page, as the widget installation docs show.
<!-- Opens the widget, default view -->
<button data-sleek>Feedback</button>
<!-- Opens a specific module -->
<button data-sleek-feedback>Feedback</button>
<button data-sleek-changelog data-badge-changelog>Changelog</button>
<button data-sleek-roadmap>Roadmap</button>
data-badge-changelog adds an unread count for new changelog entries, which is the one thing a hand-rolled button cannot do without a backend behind it. The same attribute family covers data-badge for all notifications and data-badge-feedback for feedback activity.
Whichever route you take, the accessibility floor does not move. Ship a real button, give it a name you can read, keep the target at 44 by 44, leave the focus ring alone, and tab through the page once before you call it done. If you want the wider picture of what belongs in the panel, our guide to collecting website feedback covers the part that comes after the click.