Skip to main content

Widget ideas

Feedback widget ideas: where to put it for good UX

Asklet gives you one iframe snippet. Where and how you show it is up to you: inline, a chat bubble, a side tab, a slide-in or a dialog. Here are six placements with live demos and code you can copy.

Reader feedback

This is the real widget - try it. Nothing is sent from this page.

Which placement for which site

Start with the first one for your kind of site. One placement per page is enough.

Inline

Inline, at the end of the article

The widget sits where the reading ends, as part of the page. Readers who reach the end are the ones who can judge the article, and nothing covers the content.

Best for

  • Blog posts and news
  • Documentation pages
  • Help-centre articles
  • Place it right after the last paragraph, before related posts and comments.
  • Keep it to the width of your text column (around 640px) so it reads as part of the article.
  • This is the snippet as it comes, so it needs no JavaScript and lazy-loads by itself.
Inline: HTML, CSS and JavaScript
<!-- In your article template, after the last paragraph -->
<section class="asklet-inline" aria-label="Article feedback">
  <iframe src="https://asklet.io/embed/s/YOUR-SITE-ID" title="Feedback" width="100%" height="480" referrerpolicy="unsafe-url" style="border:0" loading="lazy"></iframe>
</section>

<style>
  .asklet-inline { max-width: 640px; margin: 3rem auto; }
  .asklet-inline iframe { display: block; border-radius: 12px; }
</style>

Replace the address with your own: Websites → your website → Copy website snippet. Keep referrerpolicy="unsafe-url"; it is how the widget knows which page it is on.

Expand in place

A one-line prompt that opens in place

A single line such as "Was this page helpful?" that opens the widget when clicked. It takes almost no space, which suits pages where every pixel counts.

Best for

  • Docs and reference pages
  • Short pages
  • Pages with a lot going on
  • Built on the native <details> element: keyboard and screen-reader support come for free, with no JavaScript.
  • Phrase the summary as the question itself, so readers know what they are opening.
  • Fewer readers will open it than would see an inline widget, so expect fewer but more deliberate answers.
Expand in place: HTML, CSS and JavaScript
<details class="asklet-expand">
  <summary>Was this page helpful? Tell us</summary>
  <iframe src="https://asklet.io/embed/s/YOUR-SITE-ID" title="Feedback" width="100%" height="480" referrerpolicy="unsafe-url" style="border:0" loading="lazy"></iframe>
</details>

<style>
  .asklet-expand { max-width: 640px; margin: 3rem auto; border: 1px solid #e5e7eb; border-radius: 12px; }
  .asklet-expand > summary { cursor: pointer; padding: 1rem 1.25rem; font-weight: 600; }
  .asklet-expand > summary:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
  .asklet-expand iframe { display: block; border-top: 1px solid #e5e7eb; }
</style>

Replace the address with your own: Websites → your website → Copy website snippet. Keep referrerpolicy="unsafe-url"; it is how the widget knows which page it is on.

Chat bubble

A floating bubble, like a chat launcher

A small button in the corner that opens the widget in a panel. It is always there without being in the way, and readers open it when they have something to say.

Best for

  • Landing and marketing pages
  • Product and pricing pages
  • Whole-site feedback
  • The iframe only loads the first time someone opens the panel, so it costs nothing on page load.
  • On phones the panel becomes a bottom sheet, so it never sits half off-screen.
  • In Appearance, pick the minimal header style: it swaps the coloured header for a plain heading, which sits better under the panel's own title bar.
  • Don't combine it with a chat tool already in the same corner - move one of them.
Chat bubble: HTML, CSS and JavaScript
<div class="asklet-bubble">
  <div class="asklet-bubble__panel" id="asklet-bubble-panel" role="dialog" aria-label="Feedback" hidden>
    <div class="asklet-bubble__bar">
      <span>Feedback</span>
      <button type="button" class="asklet-bubble__close" aria-label="Close feedback">&times;</button>
    </div>
    <iframe data-src="https://asklet.io/embed/s/YOUR-SITE-ID" title="Feedback" width="100%" height="480" referrerpolicy="unsafe-url" style="border:0"></iframe>
  </div>
  <button type="button" class="asklet-bubble__toggle" aria-expanded="false" aria-controls="asklet-bubble-panel">
    Feedback
  </button>
</div>

<style>
  .asklet-bubble { position: fixed; right: 20px; bottom: 20px; z-index: 1000; font: 600 15px/1.2 system-ui, sans-serif; }
  .asklet-bubble__toggle { padding: 12px 20px; border: 0; border-radius: 999px; background: #0f766e; color: #fff; cursor: pointer; box-shadow: 0 8px 24px rgb(0 0 0 / 0.18); }
  .asklet-bubble__panel { position: absolute; right: 0; bottom: calc(100% + 12px); width: 360px; max-width: calc(100vw - 40px); overflow: hidden; background: #fff; border-radius: 16px; box-shadow: 0 16px 48px rgb(0 0 0 / 0.22); }
  .asklet-bubble__panel[hidden] { display: none; }
  .asklet-bubble__bar { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px 10px 16px; border-bottom: 1px solid #e5e7eb; }
  .asklet-bubble__close { width: 32px; height: 32px; border: 0; border-radius: 8px; background: none; font-size: 22px; cursor: pointer; }
  .asklet-bubble iframe { display: block; }
  .asklet-bubble :focus-visible { outline: 2px solid #0f766e; outline-offset: 2px; }
  @media (max-width: 480px) {
    .asklet-bubble__panel { position: fixed; left: 0; right: 0; bottom: 0; width: auto; max-width: none; border-radius: 16px 16px 0 0; }
    .asklet-bubble iframe { height: min(480px, 75vh); }
  }
</style>

<script>
  (() => {
    const root = document.querySelector('.asklet-bubble');
    const panel = root.querySelector('.asklet-bubble__panel');
    const toggle = root.querySelector('.asklet-bubble__toggle');
    const frame = panel.querySelector('iframe');

    function setOpen(open) {
      if (open && !frame.src) frame.src = frame.dataset.src; // load on first open
      panel.hidden = !open;
      toggle.setAttribute('aria-expanded', String(open));
      if (open) frame.focus();
      else toggle.focus();
    }

    toggle.addEventListener('click', () => setOpen(panel.hidden));
    root.querySelector('.asklet-bubble__close').addEventListener('click', () => setOpen(false));
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && !panel.hidden) setOpen(false);
    });
  })();
</script>

Replace the address with your own: Websites → your website → Copy website snippet. Keep referrerpolicy="unsafe-url"; it is how the widget knows which page it is on.

Side tab

A tab on the edge that slides a drawer in

A slim "Feedback" tab on the edge of the screen. Clicking it slides a drawer in from the side with the widget inside. It stays out of the reading area until someone asks for it.

Best for

  • Web apps and dashboards
  • Long-form guides
  • Sites that already use the corners
  • The closed drawer is inert, so keyboard and screen-reader users never tab into it by accident.
  • The slide is skipped for readers who turned on "reduce motion".
  • Keep the tab on the side your content doesn't use for navigation or a table of contents.
Side tab: HTML, CSS and JavaScript
<button type="button" class="asklet-tab" aria-expanded="false" aria-controls="asklet-drawer">Feedback</button>

<aside class="asklet-drawer" id="asklet-drawer" aria-label="Feedback" inert>
  <div class="asklet-drawer__bar">
    <span>Feedback</span>
    <button type="button" class="asklet-drawer__close" aria-label="Close feedback">&times;</button>
  </div>
  <iframe data-src="https://asklet.io/embed/s/YOUR-SITE-ID" title="Feedback" width="100%" height="480" referrerpolicy="unsafe-url" style="border:0"></iframe>
</aside>

<style>
  .asklet-tab { position: fixed; right: 0; top: 50%; translate: 0 -50%; z-index: 1000; writing-mode: vertical-rl; padding: 16px 10px; border: 0; border-radius: 10px 0 0 10px; background: #0f766e; color: #fff; font: 600 14px/1 system-ui, sans-serif; cursor: pointer; }
  .asklet-drawer { position: fixed; right: 0; top: 50%; z-index: 1001; width: 380px; max-width: 100vw; translate: 100% -50%; overflow: hidden; background: #fff; border-radius: 16px 0 0 16px; box-shadow: -16px 0 48px rgb(0 0 0 / 0.18); transition: translate 0.25s ease; font: 600 15px/1.2 system-ui, sans-serif; }
  .asklet-drawer.is-open { translate: 0 -50%; }
  .asklet-drawer__bar { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px 10px 16px; border-bottom: 1px solid #e5e7eb; }
  .asklet-drawer__close { width: 32px; height: 32px; border: 0; border-radius: 8px; background: none; font-size: 22px; cursor: pointer; }
  .asklet-drawer iframe { display: block; }
  .asklet-tab:focus-visible, .asklet-drawer :focus-visible { outline: 2px solid #0f766e; outline-offset: 2px; }
  @media (max-width: 480px) { .asklet-drawer { border-radius: 0; } .asklet-drawer iframe { height: min(480px, 80vh); } }
  @media (prefers-reduced-motion: reduce) { .asklet-drawer { transition: none; } }
</style>

<script>
  (() => {
    const tab = document.querySelector('.asklet-tab');
    const drawer = document.getElementById('asklet-drawer');
    const frame = drawer.querySelector('iframe');

    function setOpen(open) {
      if (open && !frame.src) frame.src = frame.dataset.src; // load on first open
      drawer.classList.toggle('is-open', open);
      drawer.inert = !open;
      tab.setAttribute('aria-expanded', String(open));
      if (open) frame.focus();
      else tab.focus();
    }

    tab.addEventListener('click', () => setOpen(!drawer.classList.contains('is-open')));
    drawer.querySelector('.asklet-drawer__close').addEventListener('click', () => setOpen(false));
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && drawer.classList.contains('is-open')) setOpen(false);
    });
  })();
</script>

Replace the address with your own: Websites → your website → Copy website snippet. Keep referrerpolicy="unsafe-url"; it is how the widget knows which page it is on.

Slide-in

A card that slides in when the reader reaches the end

A small card slides into the corner once the reader scrolls to the end of the article - the moment they can judge it - and never before. It appears once per page view.

Best for

  • Long articles and guides
  • Pages where the end is followed by a busy footer
  • Raising response rates on key content
  • It is triggered by reaching a marker at the end of the article, not by a timer, so nobody is interrupted mid-read.
  • It never takes focus, so readers who don't want it can simply carry on.
  • Dismissing it is not remembered: storing that would mean putting something in the reader's browser. Readers who already answered see the widget's "thanks, you already answered" message.
  • Use it instead of the inline widget on a page, not as well as it.
Slide-in: HTML, CSS and JavaScript
<!-- 1. Put this marker where the article ends -->
<div id="asklet-end" aria-hidden="true"></div>

<!-- 2. The card, anywhere in the page -->
<aside class="asklet-slidein" aria-label="Article feedback" inert>
  <button type="button" class="asklet-slidein__close" aria-label="Dismiss feedback">&times;</button>
  <iframe data-src="https://asklet.io/embed/s/YOUR-SITE-ID" title="Feedback" width="100%" height="480" referrerpolicy="unsafe-url" style="border:0"></iframe>
</aside>

<style>
  .asklet-slidein { position: fixed; right: 20px; bottom: 20px; z-index: 1000; width: 360px; max-width: calc(100vw - 40px); overflow: hidden; background: #fff; border-radius: 16px; box-shadow: 0 16px 48px rgb(0 0 0 / 0.22); opacity: 0; translate: 0 24px; visibility: hidden; transition: opacity 0.3s ease, translate 0.3s ease, visibility 0.3s; }
  .asklet-slidein.is-open { opacity: 1; translate: 0 0; visibility: visible; }
  .asklet-slidein__close { position: absolute; top: 8px; right: 8px; z-index: 1; width: 32px; height: 32px; border: 0; border-radius: 999px; background: rgb(255 255 255 / 0.9); font-size: 20px; cursor: pointer; }
  .asklet-slidein__close:focus-visible { outline: 2px solid #0f766e; outline-offset: 2px; }
  .asklet-slidein iframe { display: block; height: 400px; }
  @media (max-width: 480px) { .asklet-slidein { right: 0; bottom: 0; left: 0; width: auto; max-width: none; border-radius: 16px 16px 0 0; } }
  @media (prefers-reduced-motion: reduce) { .asklet-slidein { transition: none; } }
</style>

<script>
  (() => {
    const card = document.querySelector('.asklet-slidein');
    const frame = card.querySelector('iframe');
    const observer = new IntersectionObserver((entries) => {
      if (!entries.some((entry) => entry.isIntersecting)) return;
      observer.disconnect(); // once per page view
      frame.src = frame.dataset.src;
      card.inert = false;
      card.classList.add('is-open');
    });
    observer.observe(document.getElementById('asklet-end'));

    card.querySelector('.asklet-slidein__close').addEventListener('click', () => {
      card.classList.remove('is-open');
      card.inert = true;
    });
  })();
</script>

Replace the address with your own: Websites → your website → Copy website snippet. Keep referrerpolicy="unsafe-url"; it is how the widget knows which page it is on.

What to avoid

These patterns cost you answers and readers' goodwill.

  • Popups on page load

    Asking before anyone has read anything gets reflex closes, not opinions. Wait until the reader reaches the end or finishes a task.

  • Exit-intent popups

    Catching someone on the way out is annoying, and they leave anyway. Ask while they are still engaged.

  • Covering the content on phones

    A panel that hides the article on a small screen gets closed straight away. Use a bottom sheet, and always show a close button.

  • Two widgets on one page

    An inline widget and a bubble on the same page ask twice and split your answers. Pick one placement per page.

  • Reappearing after a close

    If someone dismissed it, don't bring it back on the same page. Respect the no.

  • Tracking to decide when to ask

    Remembering who was asked in cookies or local storage brings consent back into the picture. Asklet doesn't need it, so your placement shouldn't either.

Common questions

Do these placements need a different snippet?

No. Every placement uses the same iframe and the same website snippet. Only the HTML and CSS around it change, so the right survey still shows on each page.

Why does the code use data-src instead of src?

The bubble, side tab, slide-in and dialog set the iframe address when they open. The widget only loads for readers who open it, and it reads the page path at that moment.

Do I have to keep referrerpolicy="unsafe-url"?

Yes. Browsers only pass the page path to the widget with that attribute, and the path is what picks the right survey for the page.

Can the widget close itself when the reader is done?

Not yet. The widget shows a thank-you message when the reader finishes, and the reader closes the panel. That is also why the panels have a fixed height.

Will a slide-in or bubble hurt my page speed?

No. In these examples the iframe only loads when the panel opens, and the inline versions use loading="lazy".

New to the names? See what a feedback widget is, or add a feedback button or side tab to your site. Setting up on a specific platform? See the integration guides, or the widget design options. To match the widget to your site, watch it restyled in a 1-minute video.

Get your first answers today.

Set-up takes a few minutes, and the free plan covers one website or app.

Start free

Free plan, no card needed.

en