Styling the widget
Every public element of the widget exposes a stable .opini-* class. Paste your own CSS into the dashboard's Custom CSS field and it lands inside the widget's shadow root, scoped to the widget — your host page is untouched.
Selectors
Target these classes from your custom CSS. The contract is stable across minor releases — we add new classes, but never rename or remove existing ones without a major bump.
| Element | Class | Notes |
|---|---|---|
| Launcher button | .opini-trigger | The pill that floats in the corner. |
| Popover container | .opini-popover | The whole feedback modal. |
| Anchor arrow | .opini-arrow | The little notch pointing at the launcher. |
| Close button | .opini-close | The × in the corner of the popover. |
| Form heading | .opini-title | The big text at the top of the modal. |
| Form subtitle | .opini-sub | The smaller line under the heading. |
| Form | .opini-form | The <form> element wrapping all inputs. |
| Message textarea | .opini-message | Where the visitor types feedback. |
| Name input | .opini-name | Optional name field. |
| Email input | .opini-email | Optional email field. |
| Field group wrapper | .opini-row | Wraps name + email side-by-side. |
| Image-attach region | .opini-attach-region | The dashed area where the visitor can paste, drop, or click to attach an image. |
| Empty-attach affordance | .opini-attach-empty | The clickable label shown before an image is picked. |
| Attach preview row | .opini-attach-preview | The thumbnail + filename row shown after an image is picked. |
| Attach thumbnail | .opini-attach-thumb | The 80×80 image preview inside the picked-image row. |
| Remove image button | .opini-attach-remove | The × that clears the picked image so the visitor can pick another. |
| Status / error message | .opini-msg | Inline status line under the form. |
| Error variant | .opini-msg.opini-msg-err | Same as above, but only when an error is showing. |
| Action row | .opini-actions | The bottom row holding the submit button. |
| Submit button | .opini-submit | The send button. |
| Success state | .opini-success | Replaces the form after a successful send. |
| Success checkmark | .opini-check | The big tick inside the success state. |
| Success heading | .opini-thanks | The thank-you heading. |
Older short names (.trigger, .popover, …) still ride alongside the namespaced classes for one minor release. Migrate to the .opini-* contract today — the aliases drop in the release after that.
Where to paste it
Open Settings → Widget for your project, expand Custom CSS (advanced), and paste your stylesheet into the textarea. Save — the next page load on your site picks it up. The CSS is injected into the widget's shadow root after the built-in stylesheet, so source order alone is enough to override any built-in rule. No !important arms race.
Shadow DOM contains the blast radius — your CSS targets the widget and nothing else on the host page.
Examples
Three copy-pasteable starting points. Each one drops into the Custom CSS field as-is.
Centered modal
Pin the popover to the middle of the screen instead of anchoring it to the launcher corner.
/* Centered modal — drop the corner anchor and put the popover
in the middle of the screen. */
.opini-popover {
top: 50%;
left: 50%;
bottom: auto;
right: auto;
transform: translate(-50%, -50%);
width: min(440px, calc(100vw - 24px));
border-radius: 20px;
}
.opini-arrow { display: none; }
.opini-trigger {
background: #111827;
color: #f9fafb;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}Dark editorial
Serif headline, hairline border, oxblood accent. Pair with widget_theme = "dark".
/* Dark editorial — serif headline, hairline border, oxblood
accent. Pair with widget_theme = "dark" in Settings. */
.opini-popover {
background: #0b0b0d;
border: 1px solid #1f1f24;
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
}
.opini-title {
font-family: "Playfair Display", "Georgia", serif;
font-weight: 600;
font-size: 22px;
letter-spacing: -0.02em;
}
.opini-sub { color: #8b8b95; font-style: italic; }
.opini-submit {
background-image: none !important;
background: #7c1d2a;
border-radius: 4px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.opini-submit:hover { background: #92242f; }Slim corner toast
Shrink the launcher to a circle and the popover to a compact textarea — for sites where feedback is a quick aside.
/* Slim corner toast — hide the title + subtitle, shrink the
popover, and turn the launcher into a single ▸ character. */
.opini-popover {
width: 280px;
padding: 14px 14px 10px;
border-radius: 12px;
}
.opini-title, .opini-sub { display: none; }
.opini-message { min-height: 64px; font-size: 14px; }
.opini-row { display: none; }
.opini-trigger {
width: 36px;
height: 36px;
padding: 0;
border-radius: 999px;
font-size: 0;
}
.opini-trigger::before {
content: "▸";
font-size: 18px;
line-height: 1;
}Limits
- Up to 32 KB of CSS per project.
</styleand<scriptare rejected (they would break out of the injected<style>tag).- NUL bytes are rejected.
- Custom fonts:
@importa Google Fonts URL inside your CSS — the widget will fetch them from the customer's browser. Opini doesn't host font files. - We don't parse the CSS — invalid CSS just doesn't render, same as anywhere else.