Popover feedback widget
The bottom-right widget every app grows eventually: a rating, a comment, and the page URL sent along so you know where the feedback came from. The panel scales in from the button rather than from its own centre, which is the difference between it appearing and it arriving. Tailwind and Alpine come from a CDN so the file runs as-is; drop the CDN script if your site already builds Tailwind.
TailwindCSS · Alpine.js · HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Feedback widget</title>
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://unpkg.com/alpinejs@3/dist/cdn.min.js"></script>
<style>
body { margin: 0; }
[x-cloak] { display: none !important; }
</style>
</head>
<body>
<main class="min-h-screen bg-zinc-100 p-10">
<h1 class="text-xl font-semibold text-zinc-900">Your app</h1>
<p class="mt-1 text-sm text-zinc-500">The widget sits in the bottom right corner.</p>
</main>
<div x-data="{ open: false, sent: false }" @keydown.escape.window="open = false" class="fixed bottom-5 right-5">
<div
x-show="open"
x-transition:enter="transition ease-[cubic-bezier(0.23,1,0.32,1)] duration-200"
x-transition:enter-start="opacity-0 scale-95 translate-y-1"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-end="opacity-0 scale-95"
class="mb-2 w-80 origin-bottom-right rounded-xl border border-zinc-200 bg-white p-4"
x-cloak
>
<form action="https://app.portform.co/api/submit" method="POST" @submit.prevent="
fetch($el.action, { method: 'POST', body: new FormData($el) }).then(() => sent = true)
">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="checkbox" name="botcheck" class="hp" tabindex="-1" autocomplete="off" />
<input type="hidden" name="page" :value="window.location.href" />
<template x-if="!sent">
<div>
<p class="text-sm font-medium text-zinc-900">How is this page working for you?</p>
<div class="mt-3 flex gap-1.5">
<template x-for="n in 5" :key="n">
<label class="flex-1">
<input type="radio" name="rating" :value="n" class="peer sr-only" required />
<span
class="block cursor-pointer rounded-lg border border-zinc-300 py-2 text-center text-sm tabular-nums text-zinc-700 peer-checked:border-zinc-900 peer-checked:bg-zinc-900 peer-checked:text-white"
x-text="n"
></span>
</label>
</template>
</div>
<textarea
name="message" rows="3" placeholder="Anything you'd change?"
class="mt-3 w-full rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none focus:border-transparent focus:outline-2 focus:outline-zinc-900"
></textarea>
<button type="submit" class="mt-3 w-full rounded-lg bg-zinc-900 px-4 py-2 text-sm font-medium text-white transition active:scale-[0.98] hover:bg-zinc-700">
Send feedback
</button>
</div>
</template>
<template x-if="sent">
<p class="py-6 text-center text-sm text-zinc-600">Thanks, that went straight to the team.</p>
</template>
</form>
</div>
<button
@click="open = !open"
class="ml-auto flex rounded-full bg-zinc-900 px-4 py-2.5 text-sm font-medium text-white transition active:scale-[0.98] hover:bg-zinc-700"
x-text="open ? 'Close' : 'Feedback'"
>Feedback</button>
</div>
</body>
</html>Replace YOUR_ACCESS_KEY with the access key from your dashboard. Create one free. The endpoint and the honeypot are already in place.
Useful for
The part behind every one of these is a form backend. This template posts to a PortForm form endpoint, which stores each submission and sends it to your email; the docs cover the fields and responses.
Other templates
- Basic HTML contact formHTML · CSS
- Ajax contact formHTML · CSS · JavaScript
- JavaScript form validationHTML · CSS · JavaScript
- Multi-column contact formHTML · CSS
- Masked phone inputHTML · CSS · JavaScript
- Contact form with dark modeHTML · CSS
- Raw contact form (no CSS)HTML
- Multi-step NPS surveyHTML · CSS · JavaScript
- Multi-step survey formVue · HTML · CSS
- Contact form popupTailwindCSS · Alpine.js · HTML
- File upload formHTML · CSS
- JavaScript file upload formHTML · CSS · JavaScript
- React contact formReact · HTML · CSS