A simple & customizable feedback widget for your Nuxt apps.
- Features
- Quick Setup
- Configuration
- Usage
- Submission Methods
- Examples
- Known Issues
- Troubleshooting
- Contribution
- License
- 💚 Beautiful & accessible UI based on Shadcn-Vue & Tailwind CSS 4
- Built-in dark mode support with class (.dark)
- 🔩 Customizable
<FeedbackWidget />component - 📲 Scope feedback to specific features/topics
- 📧 Multiple submission methods supported:
- Email (via Resend)
- GitHub Issues
- Custom Handler
- 🌟 Handy composable for controlling widget
I recommend reading the Known Issues section before setting up the module.
Add the module to your Nuxt application in one command:
npx nuxt module add @minch/nuxt-feedbackTo add manually, install the @minch/nuxt-feedback package using your package manager of choice and add it to your nuxt.config.ts:
export default defineNuxtConfig({
modules: ["@minch/nuxt-feedback"],
feedbackWidget: {
method: "email", // Required: Choose your submission method
siteName: "My App", // Optional: Default is "Your Nuxt App"
},
});Define environment variables for your selected method. Read more below.
That's it! You can now start using <FeedbackWidget /> in your components.
Configure the module in your nuxt.config.ts under the feedbackWidget property:
export default defineNuxtConfig({
modules: ["@minch/nuxt-feedback"],
feedbackWidget: {
method: "email", // Required: 'email' | 'github' | 'custom-endpoint'
siteName: "My App", // Optional: Used in emails and GitHub issues
customEndpoint: "/api/custom-handler", // Required only for 'custom-endpoint' method
},
});| Option | Type | Required | Default | Description |
|---|---|---|---|---|
method |
'email' | 'github' | 'custom-endpoint' |
✅ | - | Feedback submission method |
siteName |
string |
❌ | "Your Nuxt App" |
Site name used in feedback submissions |
customEndpoint |
string |
❌ | - | Custom endpoint path (required when method is 'custom-endpoint') |
Depending on your chosen method, add the required environment variables to your runtimeConfig:
export default defineNuxtConfig({
// ... module config above
runtimeConfig: {
// For Email method (via Resend)
resendApiKey: process.env.RESEND_API_KEY,
resendFrom: process.env.RESEND_FROM_EMAIL,
resendTo: process.env.RESEND_TO_EMAIL,
// For GitHub method
githubToken: process.env.GITHUB_TOKEN,
githubRepo: process.env.GITHUB_REPO,
githubOwner: process.env.GITHUB_OWNER,
},
});Email Method (Resend)
RESEND_API_KEY: Your Resend API keyRESEND_FROM_EMAIL: Email address to send from (must be verified in Resend)RESEND_TO_EMAIL: Email address to receive feedback
GitHub Method
GITHUB_TOKEN: GitHub personal access token with repo permissionsGITHUB_REPO: Repository name (e.g., "my-project")GITHUB_OWNER: Repository owner username or organization
Add the widget to your layout or page:
<template>
<div>
<!-- Your content -->
<FeedbackWidget />
</div>
</template>The <FeedbackWidget /> component accepts several props for customization:
<template>
<FeedbackWidget
title="Send us feedback!"
description="We value your thoughts and suggestions."
trigger-label="💬 Feedback"
trigger-class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
submit-label="Send Feedback"
with-topics
:topics="['General', 'Bug Report', 'Feature Request', 'UI/UX']"
/>
</template>interface FeedbackUIProps {
title?: string;
description?: string;
triggerLabel?: string;
triggerClass?: string;
submitLabel?: string;
withTopics?: boolean;
topics?: string[];
}| Prop | Type | Default | Description |
|---|---|---|---|
title |
string |
"Feedback" |
Title displayed in the feedback modal |
description |
string |
"Tell us what you think." |
Description text in the modal |
triggerLabel |
string |
"Feedback" |
Text for the trigger button |
triggerClass |
string |
"" |
Additional CSS classes for the trigger button |
submitLabel |
string |
"Submit" |
Text for the submit button |
withTopics |
boolean |
true |
Whether to show the topics selector |
topics |
string[] |
["General Feedback", "Bug Report", "Feature Request"] |
Array of available feedback topics |
Control the widget programmatically using the useFeedbackWidget composable:
<script setup lang="ts">
const { isOpen, openWidget, closeWidget } = useFeedbackWidget();
// Open widget programmatically
function handleButtonClick() {
openWidget();
}
</script>
<template>
<div>
<button @click="handleButtonClick">Give Feedback 💝</button>
<!-- Widget state -->
<p v-if="isOpen">Widget is currently open</p>
</div>
</template>Note
You still need to add the
<FeedbackWidget />component to your app somewhere. If you would like to hide the default trigger and programmatically toggle the widget using your means, you can hide it by passing styles to thetriggerClassprop.
interface UseFeedbackWidget {
isOpen: Ref<boolean, boolean>; // Current widget state
openWidget: () => void; // Open the widget
closeWidget: () => void; // Close the widget
// For Internal Use
isWidgetMounted: Readonly<Ref<boolean, boolean>>;
registerWidget: () => void;
unregisterWidget: () => void;
}Sends feedback via email using the Resend service.
Setup:
- Sign up for Resend
- Get your API key and verify your domain
- Set environment variables:
RESEND_API_KEY=your_api_key
RESEND_FROM_EMAIL=[email protected]
RESEND_TO_EMAIL=[email protected]You can learn more about Resend from their docs
Configuration:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@minch/nuxt-feedback"],
feedbackWidget: {
method: "email",
siteName: "My Awesome App"
}
runtimeConfig: {
resendApiKey: process.env.RESEND_API_KEY,
resendFrom: process.env.RESEND_FROM_EMAIL,
resendTo: process.env.RESEND_TO_EMAIL
}
});Creates GitHub issues for each feedback submission.
Setup:
- Create a GitHub personal access token (PAT) with read and write permissions to a repo of your choice.
- Set environment variables:
GITHUB_TOKEN=your_github_token
GITHUB_REPO=your-repo-name # Can be a private repo
GITHUB_OWNER=your-username-or-orgConfiguration:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@minch/nuxt-feedback"],
feedbackWidget: {
method: "github",
siteName: "My Awesome App"
}
runtimeConfig: {
githubToken: process.env.GITHUB_TOKEN,
githubRepo: process.env.GITHUB_REPO,
githubOwner: process.env.GITHUB_OWNER
}
});Forwards feedback to your own API endpoint for custom processing.
Configuration:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@minch/nuxt-feedback"],
feedbackWidget: {
method: "custom-endpoint",
customEndpoint: "/api/my-custom-handler",
siteName: "My Awesome App",
},
});Feedback Data Structure:
interface FeedbackDataType {
topic: string; // Selected topic
reaction: string; // Feedback reaction
message: string; // User's optional message
siteName: string; // Your configured site name
metadata: {
route: {
fullPath: string;
hash: string;
query: LocationQuery;
name: RouteRecordNameGeneric;
path: string;
redirectedFrom: RouteLocationGeneric | undefined;
};
time: {
timestamp: string; // ISO string
timezone: string; // User's timezone
};
};
}Imporant: The feedback data is forwarded as is to your custom endpoint. Since user input can't be trusted, you'll need to implement some form of sanitization in your endpoint.
Example Custom Handler:
// ~/server/api/my-custom-handler.ts
export default defineEventHandler(async (event) => {
try {
const rawFeedback = await readBody(event);
// Sanitize input
const feedback = sanitizer(rawFeedback);
// Save to database
await db.insert(feedback);
// Send to analytics
await $fetch("/api/analytics", {
method: "POST",
body: {
event: "feedback_submitted",
properties: {
topic: feedback.topic,
route: feedback.metadata.route.path,
},
},
});
return { success: true };
} catch (error) {
throw createError({
statusCode: 500,
statusMessage: "Failed to process feedback",
});
}
});Note
Your server route doesn't need to return anything.
All error is handled at the default endpoint that sends the request to your custom endpoint. When an error occurs in your endpoint, it's a good idea to either return or throw the error. That way the appropriate success/failure messages will be displayed to the user.
Minimal trigger:
<template>
<FeedbackWidget
trigger-label="?"
trigger-class="fixed bottom-4 right-4 w-12 h-12 rounded-full bg-blue-500 text-white shadow-lg hover:bg-blue-600 transition-colors"
/>
</template>Integrated in navigation:
<template>
<FeedbackWidget
trigger-label="Feedback"
trigger-class="nav-link"
title="Help us improve"
description="Your feedback helps us build a better product"
/>
</template><template>
<FeedbackWidget
:topics="['Account', 'AI', 'Billing', 'Documentation', 'Observability']"
with-topics
title="Report an Issue"
description="Let us know what's not working"
/>
</template>This approach currently has some issues.
<script setup lang="ts">
const { openWidget } = useFeedbackWidget();
// Show feedback after user completes action
async function handleTaskComplete() {
await saveUserProgress();
// Prompt for feedback
setTimeout(() => {
openWidget();
}, 1000);
}
</script>A list of issues I have noticed and I'm working on a fix for:
- The widget doesn't currently work on fully static sites such as ones deployed to GitHub Pages as it requires a server for submitting feedback.
- The
useFeedbackWidgetcomposable doesn't detect the<FeedbackWidget />component properly.
Feel free to open an issue if you are not able to resolve an issue.
Widget not appearing:
- Ensure the module is properly added to your
nuxt.config.ts - Check that you've set a valid
methodin your configuration - Verify the component is imported (it should be auto-imported) and added to your app
Styling conflicts:
- If you have custom Tailwind config, ensure compatibility
- Use
triggerClassprop to override default styling
Submission failures:
- Check your environment variables are set correctly
- Verify API keys and tokens have proper permissions
- Check the browser console and server logs for errors
Multiple widgets appearing:
- Make sure you only have one
<FeedbackWidget />component per page. This issue will likely be resolved in a future release.
Method not found:
- Ensure your
methodis one of:'email','github', or'custom-endpoint' - Check spelling in your configuration
The module includes built-in error handling:
- Invalid configurations show warnings in development
- Failed submissions display user-friendly error messages
- Server errors are logged for debugging
If you encounter issues, check:
- Browser developer console
- Server logs
- Network tab for API request failures
Local development
# Install dependencies
pnpm install
# Generate type stubs
pnpm run dev:prepare
# Develop with the playground
pnpm run dev
# Build the playground
pnpm run dev:build
# Run ESLint
pnpm run lint
# Run Vitest
pnpm run test
pnpm run test:watch
# Release new version
pnpm run release© 2025-present Dawit Urgessa
