Custom CSS — fine-tune the widget's look

Paste a CSS string in admin → Widget → Custom CSS. It's injected inside the widget iframe's <head> last, so it wins specificity ties against our defaults.

When to reach for it

Use custom CSS when the Appearance panel can't get you what you need — typographic tweaks, specific spacing, hiding parts of the UI, matching a non-trivial brand system. Don't reach for it first: the Appearance panel handles the common 90% and keeps your widget benefiting from our default styling improvements over time. Custom CSS is a blunt instrument — once you override a selector, our updates to that selector stop affecting you.

Targetable classes

The widget exposes stable, prefixed class names. The main ones:

Class What it styles
.wilow-bubble The chat launcher bubble (bottom-right by default).
.wilow-header The top bar with the bot name and close button.
.wilow-bot-name The bot's name text in the header.
.wilow-message Any chat bubble (both visitor and bot — use .wilow-message--user / .wilow-message--bot to differentiate).
.wilow-input The text input at the bottom.
.wilow-feedback-btn The 👍 / 👎 buttons on bot turns.
.wilow-handoff-cta The "Contact a human" button.
.wilow-lead-chip The "Got your details — we'll be in touch" confirmation chip.

Inspect the running widget with devtools to see the current full set. We add classes as features land; the ones above are under a compatibility promise — we won't rename them without warning.

Worked example — darker chat header

.wilow-header {
  background: #0f172a;
  color: #f8fafc;
}
.wilow-header .wilow-bot-name {
  font-weight: 600;
  letter-spacing: 0.02em;
}

Limits

  • 20 KB max. Enforced at save time; larger inputs are rejected with a clear error.
  • No @import — would fetch external CSS on every widget load.
  • url(...) only accepts https://…, data:image/…, or relative paths. javascript: and data:text/… are rejected to block script injection.
  • No expression(), behavior:, -moz-binding, <script>, </style> — historical JS-in-CSS vectors.

Validation runs server-side. A rejected input surfaces an inline error in the editor; fix it and save again.

Testing your CSS

The live preview on the Custom CSS page renders the same widget bundle visitors see, with whatever you've typed into the CSS editor — no need to save first, no devtools required. Type a rule, look at the preview, iterate. Hit Save when you're happy and it goes live on the next widget load on your site.

If the widget on your live site starts looking broken on a specific page and you can't explain it, temporarily clear the custom CSS field, save, and reload that page. If the problem goes away, your CSS is the cause; narrow down with binary search by reintroducing half the rules at a time.

Pitfalls

  • Turning the feature off preserves your CSS — if we disable custom CSS on your workspace, the stored CSS stays but the widget stops serving it on the next config fetch. Re-enabling turns it on again automatically. You won't have to re-enter anything.
  • Don't style the host page. The widget runs in an iframe, so your CSS can't leak onto the visitor's site (or be overridden by it). That's intentional.
  • Avoid !important unless necessary. We already inject your CSS last, so specificity is normally on your side. !important makes future overrides painful — prefer a more specific selector.
  • Dark-mode media queries work. @media (prefers-color-scheme: dark) runs against the visitor's OS preference — use it to match sites that already flip for dark mode.