1 GitHub star = 1 new component.

Star the repo
Overlaysoverlaysfeedbackfloatingwidgetsurvey

Floating Feedback Widget

Floating button that opens a quick rating/feedback form.

Live Preview

Usage

Copy and paste this code into your React component:

tsx
import { FloatingFeedbackWidget } from 'react-component-kit-ui/overlays';

<FloatingFeedbackWidget placeholder="How can we improve this experience?" />

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/FloatingFeedbackWidget.tsx + FloatingFeedbackWidget.css:

FloatingFeedbackWidget.tsx
'use client';

import { useEffect, useRef } from 'react';
import './FloatingFeedbackWidget.css';

/** class:list โ†’ className */
function cx(...args: unknown[]): string {
  const out: string[] = [];
  const walk = (v: unknown) => {
    if (!v) return;
    if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
    else if (Array.isArray(v)) v.forEach(walk);
    else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
  };
  args.forEach(walk);
  return out.join(' ');
}

/**
 * FloatingFeedbackWidget โ€” Fixed corner floating pill that expands into an interactive feedback survey card.
 * 
 * @param {string} title - Widget title. Default is 'Was this helpful?'.
 * @param {string} position - Screen anchor ('bottom-right' | 'bottom-left'). Default is 'bottom-right'.
 */
export interface FloatingFeedbackWidgetProps {
  title?: string;
  position?: 'bottom-right' | 'bottom-left';
}

export function FloatingFeedbackWidget({
  title = 'Send Feedback',
  position = 'bottom-right',
}: FloatingFeedbackWidgetProps) {

  // Ported from the Astro <script>: runs once after mount (DOM logic kept as in the Astro kit).
  const mounted = useRef(false);
  useEffect(() => {
    if (mounted.current) return;
    mounted.current = true;
    function setupFeedbackWidgets() {
        document.querySelectorAll('[data-feedback-widget]').forEach((widget) => {
          const trigger = widget.querySelector('[data-fb-trigger]');
          const card = widget.querySelector('[data-fb-card]') as HTMLElement;
          const close = widget.querySelector('[data-fb-close]');
          const send = widget.querySelector('[data-fb-send]');
          if (!trigger || !card) return;
    
          trigger.addEventListener('click', () => {
            card.hidden = !card.hidden;
          });
    
          close?.addEventListener('click', () => {
            card.hidden = true;
          });
    
          send?.addEventListener('click', () => {
            card.innerHTML = `<p style="text-align:center;font-size:0.85rem;color:#10b981;font-weight:700;margin:1rem 0;">โœ“ Thank you for your feedback!</p>`;
            setTimeout(() => { card.hidden = true; }, 2000);
          });
        });
      }
    
      setupFeedbackWidgets();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <div className={cx(['fb-widget', `fb-widget--${position}`])} data-feedback-widget data-rk-2e965a="">
        <button type="button" className="fb-widget__trigger" aria-label="Open feedback modal" data-fb-trigger data-rk-2e965a="">
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" data-rk-2e965a="">
            <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" data-rk-2e965a="" />
          </svg>
          <span data-rk-2e965a="">Feedback</span>
        </button>
      
        <div className="fb-widget__card" data-fb-card hidden data-rk-2e965a="">
          <div className="fb-widget__head" data-rk-2e965a="">
            <h5 className="fb-widget__title" data-rk-2e965a="">{title}</h5>
            <button type="button" className="fb-widget__close" aria-label="Close" data-fb-close data-rk-2e965a="">ร—</button>
          </div>
      
          <div className="fb-widget__emojis" data-rk-2e965a="">
            <button type="button" className="emoji-btn" aria-label="Sad" data-rk-2e965a="">๐Ÿ™</button>
            <button type="button" className="emoji-btn" aria-label="Neutral" data-rk-2e965a="">๐Ÿ˜</button>
            <button type="button" className="emoji-btn emoji-btn--selected" aria-label="Happy" data-rk-2e965a="">๐Ÿ˜Š</button>
            <button type="button" className="emoji-btn" aria-label="Love it" data-rk-2e965a="">๐Ÿคฉ</button>
          </div>
      
          <textarea placeholder="Any details to share? (optional)" className="fb-widget__textarea" rows={2} data-rk-2e965a=""></textarea>
      
          <button type="button" className="fb-widget__send" data-fb-send data-rk-2e965a="">
            Send Feedback
          </button>
        </div>
      </div>
    </>
  );
}

export default FloatingFeedbackWidget;
FloatingFeedbackWidget.css
/* FloatingFeedbackWidget โ€” ported from astro-component-kit (overlays/FloatingFeedbackWidget.astro) */

.fb-widget:where([data-rk-2e965a]) {
  position: fixed;
  z-index: 90;
  font-family: var(--font-sans, system-ui, sans-serif);
}

.fb-widget--bottom-right:where([data-rk-2e965a]) { bottom: 1.5rem; right: 1.5rem; }
.fb-widget--bottom-left:where([data-rk-2e965a])  { bottom: 1.5rem; left: 1.5rem; }

.fb-widget__trigger:where([data-rk-2e965a]) {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0.6rem 1.1rem;
  background: rgba(14, 14, 18, 0.9);
  backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--r-full, 9999px);
  color: var(--c-text-1, #ffffff);
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  transition: transform 0.2s ease, border-color 0.2s ease;
}

.fb-widget__trigger:hover:where([data-rk-2e965a]) {
  transform: translateY(-2px);
  border-color: rgba(184, 134, 11, 0.4);
  color: var(--c-primary-light, #d4a017);
}

.fb-widget__card:where([data-rk-2e965a]) {
  position: absolute;
  bottom: calc(100% + 12px);
  right: 0;
  width: 280px;
  padding: 1.2rem;
  background: rgba(12, 12, 16, 0.95);
  backdrop-filter: blur(24px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--r-xl, 20px);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.fb-widget__head:where([data-rk-2e965a]) {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.fb-widget__title:where([data-rk-2e965a]) {
  font-size: 0.88rem;
  font-weight: 800;
  color: var(--c-text-1, #ffffff);
  margin: 0;
}

.fb-widget__close:where([data-rk-2e965a]) {
  background: transparent;
  border: none;
  color: var(--c-text-3, #737373);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}

.fb-widget__emojis:where([data-rk-2e965a]) {
  display: flex;
  justify-content: space-between;
  gap: 6px;
}

.emoji-btn:where([data-rk-2e965a]) {
  flex: 1;
  font-size: 1.4rem;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--r-md, 10px);
  padding: 6px 0;
  cursor: pointer;
  transition: transform 0.2s ease, background 0.2s ease;
}

.emoji-btn:hover:where([data-rk-2e965a]),
.emoji-btn--selected:where([data-rk-2e965a]) {
  transform: scale(1.15);
  background: rgba(184, 134, 11, 0.2);
  border-color: rgba(184, 134, 11, 0.4);
}

.fb-widget__textarea:where([data-rk-2e965a]) {
  width: 100%;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--r-sm, 8px);
  color: var(--c-text-1, #ffffff);
  font-size: 0.8rem;
  padding: 8px;
  resize: none;
  outline: none;
  font-family: inherit;
}

.fb-widget__send:where([data-rk-2e965a]) {
  padding: 0.6rem;
  background: linear-gradient(135deg, #d4a017, #b8860b);
  border: none;
  border-radius: var(--r-sm, 8px);
  color: #050505;
  font-size: 0.8rem;
  font-weight: 800;
  cursor: pointer;
  transition: opacity 0.2s;
}

.fb-widget__send:hover:where([data-rk-2e965a]) {
  opacity: 0.9;
}

Quick Info

Category
Overlays
Filename
FloatingFeedbackWidget.tsx
Dependencies
None โ€” pure React + CSS
Tags
overlaysfeedbackfloatingwidgetsurvey