iAdvizeDocs

Style engagement widgets with CSS

The supported CSS surface on the chat bubble, the question bar, the inline button and Starters: eight custom properties, nine shadow parts, and what stays private.

The chat bubble, question bar, inline button and Starters render inside a shadow root, so your storefront's CSS can't reach them by accident, and can't restyle them by accident either. Two mechanisms are open on purpose:

  • CSS custom properties: recolour a widget. Use these first.
  • Shadow parts (::part): reach an element directly when a custom property doesn't cover what you need.

Both are written in your own stylesheet. Neither needs a new Content Security Policy directive: the rules are yours, served from your origin, under the style-src you already have. Nothing here replaces the dashboard's appearance fields. Reach for CSS when the structured fields don't express what you want.

The dashboard says Chat bubble, the CSS says launcher

Every identifier on this page (data-iadvize-launcher-host, the --iadvize-launcher-* properties, the launcher parts) keeps the name it shipped with. The dashboard now labels that widget format Chat bubble; nothing in the contract below changed with it, so no stylesheet needs editing.

Only what's listed here is a contract

The names on this page are the whole supported surface. Every other element, attribute, and internal detail of a widget is private and may change without notice, including in a release that carries no changelog entry. Renaming or removing anything listed here is a breaking change and is announced in the changelog.

Host selectors

Every widget the tag renders on a page has a host element you write your rules against. The two floating formats append theirs to <body>; an inline widget's host (the inline button's or Starters' alike) is at its mount point, and which selector reaches it depends on the mount mode:

SelectorElement
[data-iadvize-launcher-host]One per launcher widget rendered here
[data-iadvize-composer-host]One per question bar rendered here
iadvize-widgetAn inline button or a Starters widget mounted as our custom element (no attribute involved)
[data-iadvize-widget-host]An inline button or a Starters widget we mounted ourselves (the marker element and CSS selector modes)

All inline forms share the same host selectors, whichever inline format renders inside them. This is part of the contract, so renaming or removing any of it is a breaking change.

The tag sets outline: none !important inline on every one of these hosts (your own <iadvize-widget> included) and on the chat panel, so a page rule for outline on them has no effect. To style focus, write the rule on a part instead, for example ::part(launcher):focus-visible or ::part(starter-question):focus-visible.

The host carries no widget id, so a rule written against these selectors applies to every widget of that type on the page. If you run two launchers on one site, you can't target one of them from CSS. Configure them separately in the dashboard instead. An inline widget is the one exception in practice: in the custom-element mode you can narrow to a single widget through the slot you chose, iadvize-widget[data-iadvize-slot='product-cta'].

CSS custom properties

Recommended, because they don't depend on the widget's internal structure: if we restructure the DOM later, your rules keep working.

Only colours are exposed. Size, corner radius, and shadow are not themeable this way: set them from the Appearance tab of the widget's editor, or reach for a part.

Set a property on the host, or on any ancestor it inherits from (:root, body, a wrapper):

your-storefront.css
[data-iadvize-launcher-host] {
  --iadvize-launcher-bg: #111111;
  --iadvize-launcher-fg: #ffffff;
}

Chat bubble and inline button

PropertyPaints
--iadvize-launcher-bgThe button's background
--iadvize-launcher-fgThe button's text colour (i.e. the optional visible label)

The inline button reuses these, on purpose

There is no --iadvize-button-* set and no button part. An inline button is the chat bubble's control in a different place (a control, an icon and a label), so it carries the same two properties and the same three part names below, and every rule you already wrote against a launcher applies to it unchanged. Set them on iadvize-widget or [data-iadvize-widget-host], or on any ancestor they inherit from.

Question bar

PropertyPaints
--iadvize-composer-bgThe bar's background
--iadvize-composer-fgThe text the shopper types
--iadvize-composer-accentThe send button's circle, which is also the folded bubble's circle
--iadvize-composer-accent-fgThe arrow glyph inside the send button, and the icon in the folded bubble

These four apply in every state of the bar. Three things on the bar have no property: the shadow (including its step up while the bar is engaged), the keyboard focus ring, and the grey text the bar types at rest. Set the shadow in the dashboard's Appearance tab.

Starters

Starters reuses both sets: no `--iadvize-starters-*` property

Starters is a composite widget: an icon+label header (absent with the heading switched off), up to three starter-question chips, and a free-text field. It carries no properties of its own: the header takes --iadvize-launcher-bg/-fg (the same tinted-icon-and-label pair a chat bubble uses), the field takes --iadvize-composer-bg/-fg/-accent/-accent-fg (the same neutral-bar-plus-accented-send-button pair the question bar uses), and the chips take --iadvize-starter-bg/-fg below, same as every other format's chips. Set them on iadvize-widget or [data-iadvize-widget-host], or on any ancestor they inherit from.

What a rule on Starters' parts runs into:

  • Two layouts, switched by the card's own width. Below 480 px the chips stack one per line at full width; from 480 px they sit in a wrapping row and the field is 280 px wide when the card shows at least one chip, at most 480 px wide when it shows none. The card reads the width of ::part(starters), never the window's, so a rule that sets ::part(starters) to 400 px wide inside a 1200 px mount gets the one-column layout. These layout properties are set by the widget's own stylesheet rather than on each element, so a ::part() rule you write for them now applies where an element's own style used to win.
  • On a wide card, the field joins the chips' line when it fits. From 480 px, ::part(starter-questions) and the field are the two items of one wrapping row: the field sits after the last chip when the chips on one line, a 6 px gap and 280 px fit in the card, and below them otherwise, 280 px wide in both cases. The chips move as one block, so the field never sits beside only some of them. That row element has no part name and is private. A rule that changes the width or margins of ::part(starter-questions) or ::part(composer) can change which of the two placements you get. Hiding the chips with a rule on ::part(starter-questions) keeps the field at 280 px: the 480 px cap only applies when no chip is rendered.
  • Chips wrap onto two lines. A chip's text sits in an inner element that clamps it to two lines and ends it with an ellipsis. That element has no part name and is private.
  • The space around the card is part of the card. It is the padding of ::part(starters), set from Space below the widget, Space above the widget, Space left of the widget and Space right of the widget on the Appearance tab. It is not a margin, so the space below adds to your next element's top margin instead of merging with it, and the space above adds to the previous element's bottom margin. The side spacing sits inside the card's width, so it narrows the width the card reads to pick its layout.
  • The host is a block by default. Browsers lay out an unknown element such as iadvize-widget as inline, which would keep the space above and below from pushing your content away. Starters' own stylesheet sets the host to display: block instead. That default loses to any rule you write on the host, so your own display still applies, display: none at a breakpoint included, and so does the hidden attribute. The host we create in the marker element and CSS selector modes is a div, which was already a block.
  • No shadow on a Starters widget saved with its text sizes: its chips and field draw none, and the field carries a border instead. A Starters widget published before the text sizes keeps its shadow until it is saved again.

Starter questions

The starter-question chips a widget renders above itself. Both formats use the same two properties, set on whichever host the chips belong to.

PropertyPaints
--iadvize-starter-bgA chip's background
--iadvize-starter-fgA chip's text

Unset, a chip takes the colours resolved from the widget's Questions card on the Appearance tab: by default your secondary colour as its fill, with your font colour (or black or white, whichever reads better) as its text.

Leave a property unset and the widget renders the value resolved from its dashboard configuration and your site's branding. An unset property changes nothing.

The loader stores these as var() references, so they resolve live. A media query, a dark-mode block, or a runtime setProperty() call all take effect immediately:

your-storefront.css
@media (prefers-color-scheme: dark) {
  [data-iadvize-launcher-host] {
    --iadvize-launcher-bg: #f5f5f5;
    --iadvize-launcher-fg: #111111;
  }
}

When No icon is chosen in the Icon menu of a widget's Appearance tab, the launcher-icon element stays in the DOM but is hidden (display: none) and takes no space.

The glyph and the label share one colour

The launcher's icon is an inline <svg> whose fill is currentColor, so it follows the same colour as the text label. Setting --iadvize-launcher-fg recolours both, and so does a color declaration on ::part(launcher). There is no separate property for the glyph on its own.

Shadow parts

Nine elements carry a part name. Nothing else does.

PartElement
launcherThe <button>: a chat bubble's, or an inline button's
launcher-iconThe <svg> glyph: inside the launcher button, or inside Starters' non-interactive header
launcher-labelThe <span> holding the label (optional on a bubble and on Starters, required on an inline button)
composerThe question bar (laid out at its engaged width, clipped to its resting one), or Starters' free-text field's own wrapper
composer-inputThe text field a shopper types in
composer-sendThe send button
startersStarters' own host wrapper, selecting the whole widget
starter-questionsThe column (or, on a wide Starters card, the row) wrapping a widget's starter-question chips
starter-questionOne chip's <button>

On Starters, `launcher-icon`/`launcher-label` are not a button

Reusing these two part names does not mean Starters' icon+label header is clickable. Unlike the chat bubble and the inline button, it is plain, non-interactive markup: no click handler, no keyboard focus stop, no :focus-visible ring. Only the chips and the free-text field open the conversation. The names are reused because Shadow Parts select by attribute, not by the underlying element's interactivity, and the visual role (an icon beside a line of text) is the same one.

your-storefront.css
[data-iadvize-launcher-host]::part(launcher) {
  box-shadow: none;
  border: 2px solid #111111;
}

/* The same three parts reach an inline button. */
iadvize-widget[data-iadvize-slot='product-cta']::part(launcher) {
  border-radius: 4px;
}

[data-iadvize-launcher-host]::part(launcher-label) {
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

[data-iadvize-composer-host]::part(composer-send) {
  border-radius: 8px;
}

A ::part() rule from your stylesheet beats the inline styles the loader applies, so you don't need !important to override a property we already set. The loader sets box-shadow, border, and border-radius itself, and all three examples above still take effect as written.

To give the question bar a width other than its usual width, set it on the host, not on a part:

[data-iadvize-composer-host] {
  width: min(440px, 100% - 32px) !important;
}

The host's width is the bar's resting width. The bar stays centred on it. The !important is needed here because the loader sets the host's width inline, and unlike a ::part() rule, a rule on the host itself doesn't beat an inline style. The min() keeps a 16px gutter on each side of a narrow phone.

When a shopper engages the bar on a window wider than 767px, it still widens around its centre to the engaged width its size gives it (462px, 520px or 620px for Small, Medium and Large in a 1512px window, pending product review at other windows), within the window minus 16px on each side and never more than 640px, whatever width you gave the host. The starter-question chips follow that engaged width and wrap onto two lines at most. A host width cannot turn the widening off.

Don't narrow ::part(composer) to resize the bar. That part is the bar laid out at its engaged width and clipped to the resting one, so a width on it does not centre the bar and does not narrow what shoppers see. The same clip cuts a box-shadow and the side edges of a border put on that part, and a background there sits behind the bar's own surface layer. Use the host's width, the custom properties above and the Appearance tab instead.

Two limits are worth knowing before you build on parts:

  • A part freezes internal structure. These names are a public API we're committed to, but the elements around them aren't. Prefer a custom property when one covers your case.
  • You can't select inside a part. ::part() accepts pseudo-classes (::part(launcher):hover works) but not descendant selectors: there's no way to reach an element nested inside a named part.

The label inherits your page's font, on both the chat bubble and the inline button, because a shadow root inherits inheritable properties from its host. That's the default with no CSS from you; setting font-family on the host or on ::part(launcher-label) changes it. There is no font field in the dashboard and the tag requests no font file, so this stylesheet is the only place a face is chosen.

One thing you can only do here, and it matters for an inline button: reserve its space, so nothing on the page moves when it appears. See Reserve the space.

What this surface doesn't cover

  • The conversation panel and everything inside it. The conversation runs in a cross-origin iframe, which your CSS cannot style at all. Its colours and font come from the site's Site settings › Brand tab. See Brand identity.
  • Geometry, via custom properties. There is no custom property for size, corner radius, or shadow. Use the Appearance tab of the widget's editor, or a ::part(launcher) rule. For the question bar, the host's width and the Appearance tab are the supported routes: see above.
  • The page-side override. iadvize('set', { launcher: … }) accepts enabled, offsetBottom, and offsetSide only. There is no page-side way to change a widget's appearance from JavaScript: that's the dashboard's job, or this stylesheet's.
  • The AI-transparency disclosure. Every control's accessible name and title always carry it (the chat bubble's and the inline button's alike), and so does every starter-question chip's accessible name. No CSS you write, and no field you set, removes it.

On this page