A customizable feedback widget for web apps that allows users to submit bug reports, ideas, and general feedback.
Add the widget script to your HTML:
<script src="https://nimiq-feedback.je-cf9.workers.dev/widget.js" defer></script>
Add the widget styles to your HTML:
<link rel="stylesheet" href="https://nimiq-feedback.je-cf9.workers.dev/widget.css" />
For better performance, load both CSS and JS with deferred loading:
<!-- Preload for better performance -->
<link
rel="preload"
href="https://nimiq-feedback.je-cf9.workers.dev/widget.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="https://nimiq-feedback.je-cf9.workers.dev/widget.css" /></noscript>
<!-- Load JavaScript deferred -->
<script src="https://nimiq-feedback.je-cf9.workers.dev/widget.js" defer></script>
Mount the widget to any DOM element:
<div id="feedback-widget"></div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const widget = window.mountFeedbackWidget('#feedback-widget', {
app: 'nimiq-wallet', // 'nimiq-wallet' | 'nimiq-pay' | 'playground'
lang: 'en', // 'en' | 'es' (optional, defaults to 'en')
feedbackEndpoint: 'https://nimiq-feedback.je-cf9.workers.dev/api/feedback',
tags: ['beta', 'mobile'], // string[] (optional, defaults to []) - custom tags for submissions
initialForm: 'bug', // optional - directly show a specific form: 'bug' | 'idea' | 'feedback'
dark: false, // boolean (optional, defaults to false) - enables dark theme
})
// Use widget methods
widget.showFormGrid() // Show the form selection grid
// widget.showForm('bug'); // Show specific form: 'bug' | 'idea' | 'feedback'
// widget.closeWidget(); // Close the widget
// widget.goBack(); // Go back to form grid
// widget.destroy(); // Cleanup and unmount
})
</script>
Add these type declarations to your Vue 3 project (e.g., in env.d.ts
or types.d.ts
):
// widget.types.d.ts
export type FormType = 'bug' | 'idea' | 'feedback'
export interface WidgetProps {
app: string
lang?: string
feedbackEndpoint?: string
tags?: string[]
initialForm?: FormType
dark?: boolean
}
export interface WidgetEvents {
'form-selected': FormType
'go-back': void
'form-submitted': { success: true, data: any }
'form-error': { success: false, error: string, details?: any }
'before-submit': { formData: FormData, type: FormType, app: string }
}
export interface WidgetInstance {
showFormGrid: () => void
showForm: (type: FormType) => void
closeWidget: () => void
goBack: () => void
destroy: () => void
communication?: {
on: <K extends keyof WidgetEvents>(event: K, callback: (data: WidgetEvents[K]) => void) => void
off: <K extends keyof WidgetEvents>(event: K, callback?: (data: WidgetEvents[K]) => void) => void
emit: <K extends keyof WidgetEvents>(event: K, data: WidgetEvents[K]) => void
}
}
declare global {
interface Window {
mountFeedbackWidget: MountFeedbackWidgetFn
}
}
export type MountFeedbackWidgetFn = (selector: string, props?: WidgetProps) => WidgetInstance
export {}
Make sure to include the type declarations in your TypeScript configuration so they are recognized globally.
Listen to widget events using the communication API:
const widget = window.mountFeedbackWidget('#feedback-widget', {
app: 'nimiq-wallet'
})
// Listen to events
widget.communication?.on('form-selected', (formType) => {
console.log('Form selected:', formType)
})
widget.communication?.on('form-submitted', (data) => {
console.log('Form submitted successfully:', data)
})
widget.communication?.on('form-error', (error) => {
console.error('Form submission error:', error)
})
widget.communication?.on('before-submit', ({ formData, type, app }) => {
console.log('Before form submission:', { type, app })
// You can modify formData here to add additional data like debug logs
formData.append('debugInfo', JSON.stringify({ userAgent: navigator.userAgent }))
})
// Show specific form directly
widget.showForm('bug')
// Show form selection grid
widget.showFormGrid()
// Close the widget
widget.closeWidget()
// Navigate back to form grid
widget.goBack()
// Clean up when done
widget.destroy()
For development and testing purposes, you can use the test query parameter to test listeners and UI flow without creating actual database entries, GitHub issues, or Slack messages:
// Configure widget with test mode
const widget = window.mountFeedbackWidget('#feedback-widget', {
app: 'nimiq-wallet',
feedbackEndpoint: 'https://nimiq-feedback.je-cf9.workers.dev/api/feedback?test=true', // Add ?test=true
tags: ['testing', 'dev']
})
// All form submissions will now return mock data and trigger listeners
// without creating actual entries in the database or external services
When test=true
is added to the feedback endpoint URL:
- ✅ Form validation still runs normally
- ✅ All widget events are triggered (
form-submitted
,before-submit
, etc.) - ✅ Mock response data is returned that matches the expected structure
- ❌ No database entries are created
- ❌ No GitHub issues are created
- ❌ No Slack messages are sent
- ❌ No file uploads occur
This is perfect for testing your integration, event listeners, and UI flows during development.
The widget automatically adapts to the user's preferred color scheme (light/dark mode). In dark mode, the widget will use lighter colors for text and darker colors for input elements. This behavior is controlled by the color-scheme
CSS property.
You can override the color scheme for a specific instance of the widget by setting the color-scheme
CSS property on the widget's container:
#feedback-widget {
color-scheme: light; /* Force light mode */
/* or */
color-scheme: dark; /* Force dark mode */
}
By default, the widget will respect the user's system preferences and switch automatically between light and dark mode.