Skip to main content
opini

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.

ElementClassNotes
Launcher button.opini-triggerThe pill that floats in the corner.
Popover container.opini-popoverThe whole feedback modal.
Anchor arrow.opini-arrowThe little notch pointing at the launcher.
Close button.opini-closeThe × in the corner of the popover.
Form heading.opini-titleThe big text at the top of the modal.
Form subtitle.opini-subThe smaller line under the heading.
Form.opini-formThe <form> element wrapping all inputs.
Message textarea.opini-messageWhere the visitor types feedback.
Name input.opini-nameOptional name field.
Email input.opini-emailOptional email field.
Field group wrapper.opini-rowWraps name + email side-by-side.
Image-attach region.opini-attach-regionThe dashed area where the visitor can paste, drop, or click to attach an image.
Empty-attach affordance.opini-attach-emptyThe clickable label shown before an image is picked.
Attach preview row.opini-attach-previewThe thumbnail + filename row shown after an image is picked.
Attach thumbnail.opini-attach-thumbThe 80×80 image preview inside the picked-image row.
Remove image button.opini-attach-removeThe × that clears the picked image so the visitor can pick another.
Status / error message.opini-msgInline status line under the form.
Error variant.opini-msg.opini-msg-errSame as above, but only when an error is showing.
Action row.opini-actionsThe bottom row holding the submit button.
Submit button.opini-submitThe send button.
Success state.opini-successReplaces the form after a successful send.
Success checkmark.opini-checkThe big tick inside the success state.
Success heading.opini-thanksThe 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.

css
/* 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".

css
/* 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.

css
/* 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.
  • </style and <script are rejected (they would break out of the injected <style> tag).
  • NUL bytes are rejected.
  • Custom fonts: @import a 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.