A lightweight, framework-agnostic feedback widget that automatically creates GitHub discussions or GitHub issues. Works with any website - React, Vue, Svelte, plain HTML, or any other framework.
- Zero Dependencies - Works without any external libraries
- Universal Compatibility - Works with any framework or vanilla HTML
- GitHub Discussions - Creates threaded discussions (recommended)
- GitHub Issues - Creates trackable issues for bugs/features
- Flexible Theming - Inherit page styles, Tailwind CSS, or custom colors
- Responsive Design - Mobile-friendly with elegant vertical tab
- Lightweight - Under 20KB minified and gzipped
- Custom Triggers - Use your own buttons or the default tab
- Custom Forms - Bring your own form design
- Programmatic API - Trigger modals via JavaScript
- Accessibility Ready - Full keyboard navigation and ARIA support
Add these two lines to your HTML and you're done:
<!-- Include the widget -->
<script src="https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js"></script>
<!-- GitHub Discussions (Recommended) -->
<feedback-widget
data-repo="your-username/your-repo"
data-token="your-github-token"
data-type="discussion"
data-category="feedback"
>
</feedback-widget>npm install @samletnorge/feedback-widget<script src="node_modules/@samletnorge/feedback-widget/dist/feedback-widget.min.js"></script>
<feedback-widget data-repo="user/repo" data-token="token"></feedback-widget>Choose the right GitHub feature for your feedback collection:
| Feature | GitHub Discussions | GitHub Issues |
|---|---|---|
| Best for | General feedback, questions, community input | Bug reports, feature requests, actionable items |
| Structure | Threaded conversations, organized by category | Linear comments, organized by labels |
| Workflow | Community-driven discussions | Project management, assignees, milestones |
| Configuration | data-type="discussion"data-category="feedback" |
data-type="issue"data-labels="feedback,bug" |
| Attribute | Type | Default | Description |
|---|---|---|---|
data-repo |
String | Required | GitHub repository (owner/repo) |
data-token |
String | Required | GitHub Personal Access Token |
data-type |
String | "discussion" |
"discussion" or "issue" |
data-category |
String | "feedback" |
Discussion category name |
data-labels |
String | "feedback" |
Comma-separated issue labels |
data-title |
String | "Feedback" |
Modal title |
| Attribute | Type | Default | Description |
|---|---|---|---|
data-position |
String | "right" |
"right", "left", "bottom-right", "bottom-left" |
data-inherit-styling |
Boolean | false |
Use page CSS instead of inline styles |
data-custom-trigger |
String | null |
CSS selector for custom trigger button |
data-custom-form |
String | null |
CSS selector for custom form |
data-theme |
String | "light" |
"light" or "dark" |
| Attribute | Type | Default | Description |
|---|---|---|---|
data-primary-color |
String | "#007bff" |
Primary color (hex/rgb) |
data-background-color |
String | "#ffffff" |
Modal background color |
data-text-color |
String | "#333333" |
Text color |
data-border-color |
String | "#dddddd" |
Border color |
data-border-radius |
String | "8px" |
Border radius |
data-font-family |
String | "system-ui, -apple-system, sans-serif" |
Font family |
| Attribute | Type | Description |
|---|---|---|
data-primary-color-class |
String | CSS class for primary color (e.g., "bg-blue-600") |
data-background-color-class |
String | CSS class for background color |
data-text-color-class |
String | CSS class for text color |
data-border-color-class |
String | CSS class for border color |
Perfect integration with your existing design:
<!-- Enable inherit styling -->
<feedback-widget
data-repo="myuser/myrepo"
data-token="token"
data-inherit-styling="true"
>
</feedback-widget>Then add CSS to style the widget components:
/* Regular CSS */
.feedback-widget-modal {
background: white;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
}
.feedback-widget-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.feedback-widget-title {
font-size: 1.25rem;
font-weight: 600;
color: #111827;
margin: 0;
}
.feedback-widget-input,
.feedback-widget-textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
background: white;
}
.feedback-widget-btn-primary {
background: #3b82f6;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
cursor: pointer;
}<feedback-widget
data-primary-color-class="bg-blue-600"
data-background-color-class="bg-white dark:bg-gray-900"
data-text-color-class="text-gray-900 dark:text-white"
data-border-color-class="border-gray-200 dark:border-gray-700"
>
</feedback-widget>For Svelte/Vue with Tailwind:
<style>
/* Tailwind CSS classes */
:global(.feedback-widget-modal) {
@apply bg-white border border-gray-200 rounded-lg shadow-lg p-6;
}
:global(.feedback-widget-header) {
@apply flex justify-between items-center mb-6;
}
:global(.feedback-widget-title) {
@apply text-xl font-semibold text-gray-900 m-0;
}
:global(.feedback-widget-input),
:global(.feedback-widget-textarea) {
@apply w-full p-3 border border-gray-300 rounded-md bg-white text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500;
}
:global(.feedback-widget-btn-primary) {
@apply bg-blue-600 text-white hover:bg-blue-700;
}
:global(.feedback-widget-btn-secondary) {
@apply bg-gray-100 text-gray-700 hover:bg-gray-200;
}
</style><feedback-widget
data-primary-color="#8B5CF6"
data-background-color="#F8FAFC"
data-text-color="#1E293B"
data-border-radius="12px"
data-position="left"
data-theme="dark"
>
</feedback-widget>| CSS Class | Element | Description |
|---|---|---|
.feedback-widget-modal |
Modal container | Main modal wrapper |
.feedback-widget-header |
Header section | Contains title and close button |
.feedback-widget-title |
Modal title | H2 heading element |
.feedback-widget-close-btn |
Close button | X button in header |
.feedback-widget-form |
Form element | Main form container |
.feedback-widget-field |
Field wrapper | Contains label + input |
.feedback-widget-label |
Form labels | Label elements |
.feedback-widget-input |
Text inputs | Subject and email inputs |
.feedback-widget-textarea |
Textarea | Feedback message field |
.feedback-widget-actions |
Button container | Cancel and submit buttons |
.feedback-widget-btn |
Base button | Base class for all buttons |
.feedback-widget-btn-primary |
Submit button | Primary action button |
.feedback-widget-btn-secondary |
Cancel button | Secondary action button |
Use your own styled button instead of the default vertical tab:
<button id="my-feedback-btn">Give Feedback</button>
<feedback-widget
data-repo="myuser/myrepo"
data-token="token"
data-custom-trigger="#my-feedback-btn"
>
</feedback-widget>// Simple helper function
window.openFeedbackWidget();
// Or dispatch custom event
document.dispatchEvent(new Event('feedback-widget-open'));
// Access widget directly
const widget = document.querySelector('feedback-widget')._feedbackWidgetInstance;
widget.openModal();Provide your own completely custom form:
<div id="custom-form" style="display: none;">
<form>
<input name="subject" placeholder="What's this about?" required>
<textarea name="feedback" placeholder="Tell us more..." required></textarea>
<input name="email" placeholder="Email (optional)">
<button type="submit">Send</button>
</form>
</div>
<feedback-widget
data-custom-form="#custom-form"
data-repo="myuser/myrepo"
data-token="token"
>
</feedback-widget>- Go to GitHub Settings > Developer settings > Personal access tokens
- Click "Generate new token (classic)"
- Give it a name like "Feedback Widget"
- Select scopes:
write:discussion(required for discussions) !! NB: This is why this is recommended. It only allows writing discussions, not whole repo
- Click "Generate token"
- Copy the token (starts with
github_pat_orghp_)
Same steps as above, but you only need:
public_repo(for public repositories) orrepo(for private repos) for these i would recomend using a github finetuned token with only theissuesscope. but you need to be admin for that.
write:discussion SCOPE or issues SCOPE.
import { useEffect } from 'react';
function App() {
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js';
document.head.appendChild(script);
}, []);
return (
<div>
<h1>My React App</h1>
<feedback-widget
data-repo="myuser/myrepo"
data-token={process.env.REACT_APP_GITHUB_TOKEN}
data-type="discussion"
data-inherit-styling="true"
/>
</div>
);
}<template>
<div>
<h1>My Vue App</h1>
<feedback-widget
:data-repo="repo"
:data-token="token"
data-type="discussion"
data-category="general"
data-position="left"
/>
</div>
</template>
<script>
export default {
data() {
return {
repo: "myuser/myrepo",
token: process.env.VUE_APP_GITHUB_TOKEN
}
},
mounted() {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js';
document.head.appendChild(script);
}
}
</script><script>
import { onMount } from 'svelte';
onMount(() => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js';
document.head.appendChild(script);
});
</script>
<!-- CSS for custom styling -->
<style>
:global(.feedback-widget-modal) {
@apply bg-background border border-border rounded-lg;
}
</style>
<h1>My Svelte App</h1>
<feedback-widget
data-repo="myuser/myrepo"
data-token="token"
data-primary-color-class="bg-primary"
data-background-color-class="bg-background"
data-inherit-styling="true"
/>// app.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<feedback-widget
[attr.data-repo]="repo"
[attr.data-token]="token"
data-type="discussion"
data-inherit-styling="true">
</feedback-widget>
`
})
export class AppComponent implements OnInit {
repo = 'myuser/myrepo';
token = environment.githubToken;
ngOnInit() {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js';
document.head.appendChild(script);
}
}<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome to my website!</h1>
<script src="https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js"></script>
<feedback-widget
data-repo="myuser/myrepo"
data-token="github_pat_xxxxx"
data-type="discussion"
data-category="feedback"
></feedback-widget>
</body>
</html>Discussions are created with this format:
**Subject:** Great new feature idea
Hi! I love the new design, but I think it could use more contrast on the buttons for better accessibility.
---
Contact: user@example.com
---
Submitted via feedback widget
Page: https://mywebsite.com/dashboard
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...Issues are created with this format:
**Subject:** Bug report - Login not working
The login form doesn't submit when I click the button. I've tried multiple browsers.
---
Contact: user@example.com
---
Submitted via feedback widget
Page: https://mywebsite.com/login
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...Listen for feedback submissions:
document.addEventListener('feedback-submitted', (event) => {
console.log('Feedback received:', event.detail);
// { subject: "Bug report", feedback: "Login broken", email: "user@example.com" }
// Send to your analytics
gtag('event', 'feedback_submitted', {
'feedback_type': event.detail.subject ? 'detailed' : 'simple',
'has_email': !!event.detail.email
});
});
document.addEventListener('feedback-error', (event) => {
console.error('Feedback submission failed:', event.detail.error);
});
document.addEventListener('feedback-success', (event) => {
console.log('Feedback submitted successfully:', event.detail);
});git clone https://github.com/samletnorge/feedback-widget.git
cd feedback-widget
pnpm install
pnpm run devVisit http://localhost:8080 to see the demo.
pnpm run build # Creates dist/feedback-widget.min.js- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Add a changelog when you do something significant (using changesets):
npx changeset- Follow the prompts to describe your changes
- This will create a new file in the
.changesetdirectory - You can add multiple changesets before committing
- Include the
.changeset/files in your commit
- Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
- Wait for review and address any feedback
- Once approved, your changes will be merged and included in the next release!
MIT License - see LICENSE file for details.
- Demo: Live Demo
- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Discussions: GitHub Discussions
If this widget helped your project, please give it a FEEDBACK!
Made with ❤️ for the web community