Give feedback
People share ideas and vote on the roadmap without leaving Visualli. The Give feedback button is ours; the panel it opens is QuackBack (together.visualli.ai), embedded as an iframe and dressed in the Visualli theme through its custom CSS. Feature VI-016 · PRODUCT.
The button
- Web app:
Buttonsecondary, sizesm, iconmessage-square, label Give feedback (sentence case), floating bottom-right, 16px (space-4) from the window edges. It hides while a right-hand panel (Sources, Go deeper) is open — that panel owns the right edge. The chat dock keeps 168px clear on both sides so the Composer stays centered and never runs under the button. - Phones: no floating button — the bottom edge belongs to the Composer. Give feedback sits in the Vault footer as an icon button, next to Comfort (
VaultonFeedback). - MCP widget, Chrome extension, desktop viewer: none — people share ideas after Open in Visualli.
- It opens the panel with
Quackback('open')(QuackBack's own launcher stays off:Quackback('init', { launcher: false })). If the SDK didn't load (ad blockers), the button stays but does nothing harmful.
import { Button } from '@visualli/design-system/components';
<Button variant="secondary" size="sm" icon="message-square" onClick={() => window.Quackback?.('open')}>
Give feedback
</Button>
The panel
- Frame (host page): 360 × 560px,
radius-lgcorners,shadow-pop, anchored above the button 16px from the edge; on phones a bottom sheet (75% of the height). Pastethemes/quackback-host.cssinto the page that loads the QuackBack SDK. - Inside (QuackBack's iframe): QuackBack can't read our
tokens.css— it lives on its own domain — sothemes/quackback-theme.csscarries our light and dark token values as QuackBack's own variables:
| QuackBack | Visualli token | |
|---|---|---|
--background · --card · --popover |
canvas · surface · surface-overlay |
grounds |
--foreground · --muted-foreground |
ink · ink-muted |
text (≥ 5.6:1 in both themes) |
--primary / --primary-foreground |
action / on-action |
the one primary button — ink, never a brand color |
--secondary · --muted · --accent |
surface-sunken |
quiet fills and hovers |
--border · --input · --ring |
line · line-strong · focus |
borders, inputs (3:1), focus ring |
--destructive · --success |
danger · success |
|
--radius · --font-sans |
radius-md · Atkinson Hyperlegible Next |
- Votes look like
UpvoteCard: outlined inline-strong; voted =topic-tealwithnode-inktext (12:1). Tags look like a neutralBadge. The selected tab looks likeSegmentedControl— never a coral or brand fill behind text. - Statuses are colored in QuackBack's admin, not in CSS: Inbox
ink-subtle· Up Nextinfo· Buildwarning· Livesuccess(light values:#5f647c·#2f5a87·#8a5600·#1d7a4e).
Keeping it in sync
Both files are generated from the tokens (npm run build:quackback-theme, part of npm run build), so they change whenever a token does — then paste the new theme into QuackBack. The theme replaces the old variables, Planned status, tags & badges, filter tabs and upvote sections of QuackBack's custom CSS; keep the functional overrides (hide powered-by, sign-in labels, admin redirect, password and name cards) below it. The old span:contains(…) rule wasn't valid CSS and is gone.
themes/quackback-theme.css — QuackBack › Settings › Branding › Custom CSS
/* ==========================================================================
VISUALLI THEME FOR QUACKBACK — generated from the Visualli design system v0.1.0
(design-system/scripts/build-quackback-theme.mjs). Don't edit by hand: change the
tokens or the script, then run `npm run build:quackback-theme`.
Paste into QuackBack › Settings › Branding › Custom CSS. It replaces the old theme
sections (variables, "Planned" status, tags & badges, filter tabs, upvote button);
keep your functional overrides below it (hide powered-by, sign-in labels, admin
redirect, password and name cards).
========================================================================== */
@import url("https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible+Next:wght@400;600;700&display=swap");
/* Light — Visualli "light" theme */
:root {
--background: #f6f3ee; /* canvas */
--foreground: #182140; /* ink */
--card: #ffffff; /* surface */
--card-foreground: #182140; /* ink */
--popover: #ffffff; /* surface-overlay */
--popover-foreground: #182140; /* ink */
--primary: #182140; /* action */
--primary-foreground: #ffffff; /* on-action */
--secondary: #f0ece5; /* surface-sunken */
--secondary-foreground: #182140; /* ink */
--muted: #f0ece5; /* surface-sunken */
--muted-foreground: #4f5671; /* ink-muted */
--accent: #f0ece5; /* surface-sunken */
--accent-foreground: #182140; /* ink */
--destructive: #c22f3c; /* danger */
--destructive-foreground: #ffffff; /* surface */
--border: #e4dfd6; /* line */
--input: #7a7f94; /* line-strong */
--ring: #325e8c; /* focus */
--success: #1d7a4e; /* success */
--radius: 16px; /* radius-md: cards and the panel; controls derive smaller radii from it */
--font-sans: "Atkinson Hyperlegible Next", ui-sans-serif, system-ui, sans-serif;
font-family: var(--font-sans);
}
/* Dark — Visualli "dark" theme */
.dark {
--background: #0f1220; /* canvas */
--foreground: #eef0f7; /* ink */
--card: #171b2b; /* surface */
--card-foreground: #eef0f7; /* ink */
--popover: #212639; /* surface-overlay */
--popover-foreground: #eef0f7; /* ink */
--primary: #f6f3ee; /* action */
--primary-foreground: #182140; /* on-action */
--secondary: #0f1220; /* surface-sunken */
--secondary-foreground: #eef0f7; /* ink */
--muted: #0f1220; /* surface-sunken */
--muted-foreground: #b3b8cc; /* ink-muted */
--accent: #0f1220; /* surface-sunken */
--accent-foreground: #eef0f7; /* ink */
--destructive: #ff8a93; /* danger */
--destructive-foreground: #171b2b; /* surface */
--border: #2b3148; /* line */
--input: #6c7392; /* line-strong */
--ring: #12c7d3; /* focus */
--success: #5ed39e; /* success */
--radius: 16px;
--font-sans: "Atkinson Hyperlegible Next", ui-sans-serif, system-ui, sans-serif;
font-family: var(--font-sans);
}
/* Votes like UpvoteCard (voted = topic-teal with node ink), tags like Badge,
the selected filter tab like SegmentedControl. Topic colors stay pastel in dark mode. */
button[aria-label*="vote"],
button[class*="vote"] {
background-color: #ffffff !important;
border: 1.5px solid #7a7f94 !important;
color: #182140 !important;
border-radius: 10px !important;
}
button[aria-label*="vote"] svg,
button[class*="vote"] svg { stroke: currentColor !important; fill: none !important; color: inherit !important; }
button[aria-label*="vote"][aria-pressed="true"],
button[class*="vote"][data-state="on"],
[data-voted="true"] {
background-color: #87f2f8 !important;
border-color: #00cdd5 !important;
color: #182140 !important;
}
[class*="badge"],
[class*="tag"] {
background-color: #f0ece5 !important;
color: #4f5671 !important;
border: 1px solid #e4dfd6 !important;
border-radius: 999px !important;
}
button[role="tab"][aria-selected="true"],
.active-filter {
background-color: #ffffff !important;
color: #182140 !important;
box-shadow: 0 1px 2px rgba(24, 33, 64, 0.06), 0 8px 24px rgba(24, 33, 64, 0.08) !important;
outline: 1px solid #e4dfd6 !important;
}
.dark button[aria-label*="vote"],
.dark button[class*="vote"] {
background-color: #171b2b !important;
border: 1.5px solid #6c7392 !important;
color: #eef0f7 !important;
border-radius: 10px !important;
}
.dark button[aria-label*="vote"] svg,
.dark button[class*="vote"] svg { stroke: currentColor !important; fill: none !important; color: inherit !important; }
.dark button[aria-label*="vote"][aria-pressed="true"],
.dark button[class*="vote"][data-state="on"],
.dark [data-voted="true"] {
background-color: #87f2f8 !important;
border-color: #0fc5cd !important;
color: #182140 !important;
}
.dark [class*="badge"],
.dark [class*="tag"] {
background-color: #0f1220 !important;
color: #b3b8cc !important;
border: 1px solid #2b3148 !important;
border-radius: 999px !important;
}
.dark button[role="tab"][aria-selected="true"],
.dark .active-filter {
background-color: #171b2b !important;
color: #eef0f7 !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.40), 0 8px 24px rgba(0, 0, 0, 0.35) !important;
outline: 1px solid #2b3148 !important;
}
/* Focus like every Visualli control: a solid 2px focus ring */
:focus-visible { outline: 2px solid #325e8c !important; outline-offset: 2px !important; }
.dark :focus-visible { outline-color: #12c7d3 !important; }
themes/quackback-host.css — the host page
/* ==========================================================================
QUACKBACK PANEL FRAME (host page) — generated from the Visualli design system v0.1.0.
The panel QuackBack opens on Quackback("open"), anchored above the "Give feedback" button:
radius-lg corners, shadow-pop, 16px (space-4) from the window edge; a bottom sheet on phones.
========================================================================== */
[data-quackback-widget],
[class*="quackback"],
[id*="quackback"] {
width: 100% !important;
max-width: 360px !important;
height: 560px !important;
max-height: calc(100dvh - 96px) !important;
right: 16px !important;
bottom: 64px !important; /* clears the 32px "Give feedback" button + 16px gap + 16px edge */
border-radius: 24px !important;
box-shadow: 0 2px 4px rgba(24, 33, 64, 0.08), 0 16px 40px rgba(24, 33, 64, 0.14) !important;
overflow: hidden !important;
}
[data-quackback-widget] iframe,
[class*="quackback"] iframe,
[id*="quackback"] iframe { width: 100% !important; height: 100% !important; max-height: none !important; }
html.dark [data-quackback-widget],
html.dark [class*="quackback"],
html.dark [id*="quackback"] { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.50), 0 16px 40px rgba(0, 0, 0, 0.50) !important; color-scheme: dark !important; }
html.light [data-quackback-widget],
html.light [class*="quackback"],
html.light [id*="quackback"] { color-scheme: light !important; }
/* Phones: a bottom sheet, like every Visualli sheet */
@media (max-width: 640px) {
[data-quackback-widget],
[class*="quackback"],
[id*="quackback"] {
position: fixed !important;
left: 0 !important; right: 0 !important; bottom: 0 !important;
max-width: 100% !important;
height: 75dvh !important;
max-height: 75dvh !important;
border-radius: 24px 24px 0 0 !important;
}
}
