- CSS variables — ~31
--dw-*tokens covering colors, radius, elevation, focus rings, status colors, and motion. Set one and every element that uses it follows. - Anatomy selectors — every styled element carries a
data-dw-partattribute naming its role. Target one to restyle exactly that element.
!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.
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 thedata-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.
Target one element
Every styled element carries adata-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.
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 (1–6), 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 Progress → in-progress, 進行中 → 進行中.
Guaranteed part names
These 74 names are the public contract and won’t be renamed without a major version. The SDK renders moredata-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.
Caveats
-
Overriding
backgroundorcoloron a part also overrides that element’s:hoverand:focus-visiblefill. 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:Overridingborder-radiusorbox-shadowhas no such side effect. -
A few radii are not tokenized. Pills, dots, and avatars are fully round with no
--dw-radius-fullbehind them. To change one, target its part.