Skip to main content
Everything visual in the SDK is reachable from your CSS through two seams:
  1. CSS variables — ~31 --dw-* tokens covering colors, radius, elevation, focus rings, status colors, and motion. Set one and every element that uses it follows.
  2. Anatomy selectors — every styled element carries a data-dw-part attribute naming its role. Target one to restyle exactly that element.
Both work with plain declarations. You never need !important, and you should not use it. All SDK CSS ships inside @layer (theme, base, components, utilities), and an unlayered rule in your own stylesheet outranks every layer regardless of specificity or import order. !important inverts layer order, so it makes the SDK’s own rules win again — the opposite of what you want.
The theming contract described here ships in both @statisfy/portal-react and @statisfy/ai-react, from 0.1.0. The two stylesheets carry an identical token layer, so a page that imports both is styled once, not twice. The light palette is the default, and dark mode stays off until you opt in.

Override a variable

Declare the variable in an unlayered rule — :root, or any scope you want it confined to. It holds no matter which stylesheet loads first.
Color values are space-separated RGB triplets, not rgb() or hex — that’s what lets the SDK apply opacity internally. Write 13 148 136, never #0d9488.

Colors

If you re-point --dw-primary, keep it readable against --dw-primary-foreground (aim for at least 4.5:1) — the pair is used both as a solid fill under inverse text and as link ink on the page.

Focus ring

One rule draws the focus ring for every interactive part.

Elevation

--dw-shadow-color is the shadow ink; the four steps are complete box-shadow strings. Set a step to none to flatten that tier everywhere.

Overlay

The modal scrim. These take full color values, not triplets — the scrim carries its own alpha.

Radius

There is no --dw-radius-full: pills, dots, and avatars are fully round by design and aren’t on the radius scale. To change one, target its part.

Motion

Under prefers-reduced-motion: reduce the SDK stills its own animations for the whole widget, even if you’ve set a custom transition duration. You don’t need to add your own media query.

Dark mode (opt-in)

The SDK ships a dark palette as of 0.2.0. It’s opt-in: an embedded widget should match the page it’s mounted in, not the visitor’s OS, so nothing changes until you set the data-dw-theme attribute on your html element.
The dark palette re-points the color tokens (backgrounds, borders, status colors, --dw-shadow-color, --dw-overlay) plus --dw-color-scheme, which switches the browser-painted controls — checkbox ticks, <select> arrows, scrollbars — inside the widget without touching your page. Both dark blocks live inside @layer theme, exactly like the light defaults, so your own unlayered :root declarations still win at any OS setting.
If you opt into dark mode with a partial custom palette, own every color token or none of them. A light-tuned --dw-primary on the SDK’s dark grounds becomes unreadable, and the SDK can’t detect a half-set palette to compensate. In particular, if you re-point --dw-primary for dark, re-point --dw-primary-foreground with it.

Target one element

Every styled element carries a data-dw-part attribute naming its role in the anatomy. Select it directly — an attribute selector has the same specificity as a class, and the same unlayered-wins rule applies.
Some parts also carry state attributes you can key off: data-dw-role (user|agent), data-dw-state (loading|error|empty|ready), data-dw-status (slugified status value), data-dw-step (done|current|upcoming), data-dw-selected, data-dw-unread, data-dw-busy, data-dw-level (16), data-dw-held, data-dw-field, data-dw-type, data-dw-empty.
data-dw-status carries your own status value, lowercased with runs of separators collapsed to a single -. Letters and digits survive in any script: In Progressin-progress, 進行中進行中.

Guaranteed part names

These 74 names are the public contract and won’t be renamed without a major version. The SDK renders more data-dw-part values than these; the rest exist in the DOM but aren’t yet guaranteed, so style them at your own risk. attachment-chip · attachment-list · auth-card · auth-input · auth-submit · chat-bubble · chat-composer · chat-error · chat-header · chat-input · chat-name · chat-pane · chat-root · chat-row · chat-send · chat-toast · chat-tool-chip · comment · comment-body · comment-composer · comment-input · comment-submit · comments · comments-list · field · field-input · field-label · field-value · form-card · form-submit · history-list · history-root · history-row · journey · journey-marker · journey-step · journey-title · lightbox-overlay · lightbox-panel · md-blockquote · md-code-inline · md-heading · md-link · md-list · md-p · md-pre · md-root · md-table · messages · picker-button · picker-chip · progress · progress-fill · progress-track · progress-value · project-card · project-error · project-header · project-title · status-badge · status-dot · status-notice · status-select · status-select-control · task-detail-backdrop · task-detail-body · task-detail-panel · task-detail-save · task-detail-title · task-group · task-group-header · task-list · task-row · task-row-title

Examples

Recolor to your brand. Two variables move the majority of the surface; --dw-ring follows --dw-primary automatically.
Go square and flat. Radius and elevation both run to zero — no part selectors needed.
Restyle one element. No variable covers “make task rows a card”; reach for the part instead.

Caveats

  1. Overriding background or color on a part also overrides that element’s :hover and :focus-visible fill. Your unlayered rule beats the SDK’s layered rule in every state, not just the resting one, so re-declare the states you still want:
    Overriding border-radius or box-shadow has no such side effect.
  2. A few radii are not tokenized. Pills, dots, and avatars are fully round with no --dw-radius-full behind them. To change one, target its part.