Summary
Let owners theme the widget so it does not always look like feedback2code’s orange terminal chip. The marketing FAQ already promises this: “The widget auto-detects light and dark mode. … Custom theming options are coming soon.” (components/seo/home-next-seo.tsx).
Today colors are hardcoded in the iframe script from prefers-color-scheme (lib/widget-embed.ts). There is no configure UI and no way to force light on a dark marketing site (or vice versa).
Current behavior
Inside buildFrameWidgetInlineScript:
isDark = matchMedia("(prefers-color-scheme: dark)")
- Two palettes
C with accent #ff6b00, monospace stack Fira/JetBrains, FAB label [ feedback ], bottom-right only (safe-area-inset-*).
- Parent iframe is a large transparent hit-rect, also bottom-right (
buildParentEmbedScript).
Configure (configure/page.tsx) cannot set appearance. Widget script cache is max-age=300; iframe HTML is no-store.
hello (lib/widget-hello.ts) does not return theme.
Problem
Agencies will not paste a widget that fights the client’s brand (wrong orange, wrong corner covering a chat widget like Intercom/Crisp, dark FAB on a dark footer). This is a sales blocker, not polish.
Proposed design
v1 knobs (keep the list short)
| Setting |
Values |
Default |
| Color scheme |
system / light / dark |
system (today) |
| Accent |
hex (we validate) |
#ff6b00 |
| Position |
bottom-right / bottom-left |
bottom-right |
| Label |
string, max 24 chars |
feedback |
Explicitly not v1: arbitrary CSS injection, custom fonts, border-radius scales, hiding the brand entirely (keep a discreet “f2c” or “feedback2code” in the panel footer for trust; paid “remove branding” can be Pro later).
Config storage + delivery
Store on RepositoryConfig (JSON widgetTheme or columns). Same hello/config channel as path allowlist so changing theme does not require recopying the snippet.
Flow:
- Parent script requests config (
GET hello//h) with widget id + origin.
- Response includes
{ theme: { scheme, accent, position, label } }.
- Parent sizes/positions the iframe (left vs right).
- Parent posts theme into the iframe with the existing
f2c: "parent" message (extend payload) or the iframe fetches hello too. Prefer one fetch in the parent, then postMessage, so the iframe does not race.
If hello fails: fall back to today’s hardcoded defaults (widget still works).
Live preview on the configure page: a static mini-FAB using the same tokens. Do not iframe production feedback2code.dev into the dashboard unless easy.
Contrast / a11y
- If they pick a light accent, FAB text should flip to
#000 vs #fff (we already use btnFg black on orange). Compute contrast; if accent is too close to bg, show a configure warning.
- Keep existing
:focus outlines; do not drop aria-label on close.
- Left position must still respect
safe-area-inset-left on iOS.
Plan gating (assumption)
v1 un-gated (or gate only “remove branding”). Theme is why agencies embed. If we need a Pro hook, “custom accent” is the upsell — but shipping it on Free will convert more than locking it. Recommendation: all v1 knobs on Free.
Update FAQ from “coming soon” to the real list when this ships.
Assumptions
- We stay a designed widget (terminal-ish), not a fully white-label CSS soup. Accent + scheme + corner is 90% of Intercom-style requests.
- No runtime
window.Feedback2Code('theme', …) API in v1; configure UI is the source of truth (avoids clients checking theme into git and drifting).
label is owner-controlled and appears on someone else’s site — strip RTL override chars / XSS; it is textContent, never innerHTML.
- Dark/light follows widget setting, not the dashboard user’s OS. Client visitors get the owner’s choice when not
system.
Acceptance criteria
Out of scope
- Per-page theme overrides.
- Matching Intercom/Crisp collision avoidance (z-index is already
2147483647; document “put others lower” rather than building a collider).
- Uploading a custom FAB icon.
Implementation notes
lib/widget-embed.ts — parameterize C, FAB side, label; stop closing over hardcoded orange only.
- Parent iframe
cssText position currently bottom:0;right:0 — must switch to left.
- Shared types with hello payload and Prisma JSON.
app/embed/frame/route.ts could accept ?accent= as a cache-buster; prefer postMessage so we do not leak a huge query cache surface.
See also
- Page-path allowlist (shared hello/config endpoint — implement one config API, two features)
- Element picker (pick overlay color should use the same accent)
- Use-case landings (show a themed widget screenshot on the agency page)
Summary
Let owners theme the widget so it does not always look like feedback2code’s orange terminal chip. The marketing FAQ already promises this: “The widget auto-detects light and dark mode. … Custom theming options are coming soon.” (
components/seo/home-next-seo.tsx).Today colors are hardcoded in the iframe script from
prefers-color-scheme(lib/widget-embed.ts). There is no configure UI and no way to force light on a dark marketing site (or vice versa).Current behavior
Inside
buildFrameWidgetInlineScript:isDark = matchMedia("(prefers-color-scheme: dark)")Cwith accent#ff6b00, monospace stack Fira/JetBrains, FAB label[ feedback ], bottom-right only (safe-area-inset-*).buildParentEmbedScript).Configure (
configure/page.tsx) cannot set appearance. Widget script cache ismax-age=300; iframe HTML isno-store.hello(lib/widget-hello.ts) does not return theme.Problem
Agencies will not paste a widget that fights the client’s brand (wrong orange, wrong corner covering a chat widget like Intercom/Crisp, dark FAB on a dark footer). This is a sales blocker, not polish.
Proposed design
v1 knobs (keep the list short)
system/light/darksystem(today)#ff6b00bottom-right/bottom-leftbottom-rightfeedbackExplicitly not v1: arbitrary CSS injection, custom fonts, border-radius scales, hiding the brand entirely (keep a discreet “f2c” or “feedback2code” in the panel footer for trust; paid “remove branding” can be Pro later).
Config storage + delivery
Store on
RepositoryConfig(JSONwidgetThemeor columns). Same hello/config channel as path allowlist so changing theme does not require recopying the snippet.Flow:
GEThello//h) with widget id + origin.{ theme: { scheme, accent, position, label } }.f2c: "parent"message (extend payload) or the iframe fetches hello too. Prefer one fetch in the parent, then postMessage, so the iframe does not race.If hello fails: fall back to today’s hardcoded defaults (widget still works).
Live preview on the configure page: a static mini-FAB using the same tokens. Do not iframe production
feedback2code.devinto the dashboard unless easy.Contrast / a11y
#000vs#fff(we already usebtnFgblack on orange). Compute contrast; if accent is too close to bg, show a configure warning.:focusoutlines; do not droparia-labelon close.safe-area-inset-lefton iOS.Plan gating (assumption)
v1 un-gated (or gate only “remove branding”). Theme is why agencies embed. If we need a Pro hook, “custom accent” is the upsell — but shipping it on Free will convert more than locking it. Recommendation: all v1 knobs on Free.
Update FAQ from “coming soon” to the real list when this ships.
Assumptions
window.Feedback2Code('theme', …)API in v1; configure UI is the source of truth (avoids clients checking theme into git and drifting).labelis owner-controlled and appears on someone else’s site — strip RTL override chars / XSS; it is textContent, never innerHTML.system.Acceptance criteria
bottom-leftFAB does not cover typical left-nav; iframe hit-rect moves with it.lighton a dark OS still renders the light palette (and vice versa).C.accentusages).Out of scope
2147483647; document “put others lower” rather than building a collider).Implementation notes
lib/widget-embed.ts— parameterizeC, FAB side, label; stop closing over hardcoded orange only.cssTextposition currentlybottom:0;right:0— must switch to left.app/embed/frame/route.tscould accept?accent=as a cache-buster; prefer postMessage so we do not leak a huge query cache surface.See also