CSS Injection
This page is the configuration reference for Host-delivered CSS. JSON and TypeScript blocks show individual settings and component contracts, not a complete frontend package.
For iframe pages, the Web Host uses a layered injection pipeline to give the
child document the same visual theme as the host. Because an iframe does not
inherit CSS from its parent document, the host injects style assets into the
child's srcdoc; ProxyConfig controls those iframe layers. Web Fragment
pages use a separate delivery path described below.
This page documents the injection pipeline, all available flags, and how to customize styles at the global, host-chrome, or per-page level. It is the canonical reference for the proxy.injections CSS flags and their runtime defaults — authoring docs that show recommended explicit values link back here. For the developer-facing theming guide (CSS variable tokens, Tailwind mapping, web component patterns), see Theming.
CSS Delivery Matrix
The facade exposes theming through three scopes — global (custom_css, css_variables, icon_sets), host (host_custom_css, host_css_variables, host_icon_sets), and children (children_custom_css, children_css_variables). The Web Host composes them per surface. Two rules govern everything below:
- CSS custom properties (
*_css_variables) inherit to a WC host. WippyElement bridges names from the effective global and children/page maps through its forced-theme inner root so local theme defaults cannot reset them. This is independent ofcustomCss; host-only names rely on ordinary inheritance and can be shadowed if local theme CSS redeclares them on the inner root. - CSS selector rules (
*_custom_css) do not cross an iframe or shadow boundary by themselves. The runtime injects them into the selectedview.pagerealm and — as of Web Host 1.0.43 — into eachview.componentshadow root (opt-out via the component'scustomCssflag). Before 1.0.43, only variables reached a component shadow root.
| Facade knob | Delivers | Host shell doc | view.page child realm |
view.component shadow root |
|---|---|---|---|---|
custom_css (global) |
selector rules | ✓ injected | ✓ injected¹ | ✓ injected (1.0.43+, opt-out)¹ |
css_variables (global) |
custom properties | ✓ effective mode blocks | ✓ effective mode blocks | ✓ inherited + bridged |
host_custom_css (host) |
selector rules | ✓ injected | ✗ | ✗ |
host_css_variables (host) |
custom properties | ✓ :root |
✗ | host-mounted WCs only² |
children_custom_css (children) |
selector rules | ✗ | ✓ injected¹ | ✓ injected (1.0.43+, opt-out)¹ |
children_css_variables (children) |
custom properties | ✗ | ✓ :root |
page WCs only² |
¹ The Web Host composes what a child receives: a view.page under either
engine and a view.component get global + children custom CSS merged into
one sheet (children_custom_css appended after custom_css). The iframe and
component customCss flags are gates, not literal single-scope injects; the
Web Fragment adapter applies its composed page sheet without that iframe flag.
² A web component inherits custom properties from the :root of wherever it is mounted: a host-chrome WC inherits global + host vars from the host document; a WC inside a view.page inherits global + children vars from that page realm. The inner-root bridge covers global and children/page variable names, not host-only names. Its injected custom CSS is always the children scope (global + children). Keep shared styling in custom_css / css_variables (global) — those reach every surface regardless of mount location.
fs:// file support: the six theming knobs above accept an fs://<path> value resolved at request time from the content_fs filesystem — see Facade → Reusing facade theming on non-Web-Host pages. icon_sets / host_icon_sets and every non-theming JSON parameter are inline-only.
For more than a few overrides, keep CSS and JSON in separate files behind content_fs and reference them with fs://. This keeps theme assets reviewable and reusable. Do not substitute file://: that is a loader-time inlining mechanism, not the facade's request-time theming contract.
The iframe injection pipeline
Styles are injected in this logical layering. The first four layers are plain
<style>/<link> elements. cssVariables and the non-@import declarations
from customCSS are placed in the iframe document's adoptedStyleSheets (see
Override mechanism below), so those
declarations win regardless of <head> source order. Constructable stylesheets
cannot contain @import, so the proxy extracts those rules into an ordinary
<head> style whose cascade follows normal document order:
The view.page iframe pipeline is themeConfig → primevue/tailwind →
iframe → markdown → customVariables → customCss in logical cascade
order. Configuration precedence is separate: facade theme → page
config_overrides → runtime override decides which values become
customVariables and customCss, not where the resulting styles sit in the
iframe cascade.
1. theme-config.css — CSS custom properties (--p-primary-*, --p-surface-*, --p-secondary-*)
2. primevue.css — PrimeVue component styles scoped via those variables
tailwind.css — Tailwind utility classes (same bundle as primevue.css)
3. iframe.css — Default themed scrollbar styling (historical name; no iframe layout reset)
4. markdown.css — .data-body rendering styles for Markdown content
5. cssVariables — effective base + Auto/forced mode blocks from AppConfig.theming.global.cssVariables (adopted stylesheet)
6. customCSS — Non-@import CSS in an adopted stylesheet; extracted @import rules use a head style
This list shows the logical override order, not the literal <head> insertion
order. The adopted-stylesheet cascade determines the precedence of
cssVariables and non-@import custom declarations; extracted imports remain
ordinary document styles. See Override mechanism.
Each child iframe receives its own copies of the platform bundles enabled for that page rather than inheriting them through the host document's cascade. The Host, iframe pages, Web Fragments, and web-component shadow roots then receive their scope-specific global, host, or children customization through the delivery paths shown above; their complete style sets are not identical.
ProxyConfig.injections.css Flags
These nested flags are lower camelCase in both backend registry YAML and frontend package.json under wippy.proxy.injections.css. Facade requirement names use their documented snake_case names, while registry fields follow their individual schema. Nested proxy objects are passed through without key conversion. YAML wins per nested key. See Micro Frontend Apps (view.page) § Operator proxy override.
meta:
type: view.page
# ...
proxy:
enabled: true
injections:
css:
themeConfig: true
primevue: true
customCss: true
tailwindConfig: false
{
"wippy": {
"proxy": {
"injections": {
"css": {
"themeConfig": true,
"iframe": true,
"primevue": true,
"markdown": true,
"customCss": true,
"customVariables": true
},
"tailwindConfig": true,
"resizeObserver": true,
"preventLinkClicks": true,
"iconifyIcons": true,
"refreshWhenVisible": true,
"historyPolyfill": true,
"errorCapture": true
}
}
}
}
CSS flags
| Flag | Default | What it injects |
|---|---|---|
themeConfig |
true |
theme-config.css — all --p-primary-*, --p-surface-*, --p-secondary-*, and PrimeVue semantic variables. Disabling it removes this platform theme layer; enabled customVariables and customCss still apply independently. |
iframe |
true |
iframe.css — default themed scrollbar styling. The name is historical and does not imply iframe layout rules. Keep enabled for every page for scrollbar consistency. |
primevue |
true |
primevue.css + tailwind.css — PrimeVue component styles and Tailwind v3 utilities. Disable only while the entire artifact has no PrimeVue-like product UI. Framework choice alone is not an exception. |
markdown |
true |
markdown.css — .data-body markdown rendering styles used by chat artifact display. |
customCss |
true |
The customCSS string from the child-projected AppConfig.theming.global. |
customVariables |
true |
The child-projected cssVariables map, compiled as effective base, Auto-light/dark, and forced Light/Dark blocks for every configured custom-property name. |
There is no dedicated fonts flag. Google Fonts are delivered through theming.global.customCSS (an @import rule), which the iframe injects via the existing customCss flag.
Non-CSS injection flags
These flags sit alongside css in the injections block:
| Flag | Default | What it does |
|---|---|---|
tailwindConfig |
true |
Exposes window.tailwind.config for apps that use the CDN Tailwind runtime (<script src="https://cdn.tailwindcss.com">). Not needed for Vite builds that compile Tailwind at build time. |
resizeObserver |
true |
Observe the child document body and send size updates to the host. This is a body-size relay, not a browser API polyfill. |
preventLinkClicks |
true |
Intercept all <a> clicks inside the iframe and classify them through host.classifyLink() before navigating. Useful for pages with external Markdown content that may contain host-navigable links. |
iconifyIcons |
true |
Inject registered Iconify icon sets so <iconify-icon> elements work offline. |
refreshWhenVisible |
true |
Reload the child window when the host's @visibility event changes to true. Disable it when a retained iframe must resume without a reload. |
historyPolyfill |
true |
No-op today. The history polyfill is intentionally disabled for srcdoc iframes (window.location is non-configurable), so this flag has no runtime effect. The runtime always installs a history guard instead, which stubs window.history methods and warns to use memory-history routing — apps must use memory mode (e.g. createAppRouter memory history). Setting this flag does not make SPA route changes observable by the host. |
errorCapture |
true |
Attach window.onerror and window.onunhandledrejection handlers that forward uncaught errors to the host via logger.captureException. Enable in production for centralized error collection. |
If a page omits wippy.proxy.injections, the iframe proxy has permissive runtime defaults and enables most injections. Vite micro frontend apps should still declare the explicit values they rely on so a package review can see whether the app expects host CSS, link interception, body-size reporting, or error capture.
Web Fragment delivery
Web Fragment pages do not use the iframe CSS-injection switches. The framework
gateway adds the fixed Web Host CSS assets while it rewrites the page, and the
fragment adapter applies the effective cssVariables and customCSS as
ordinary <style> elements in the reflected head after the AppConfig
handshake. The proxy.injections.css flags therefore do not gate the platform
CSS delivered to a fragment. Fragment error capture is installed
unconditionally rather than controlled by the iframe errorCapture flag.
See Rendering Engines for the engine boundary and Framework Views for the gateway configuration.
Disabling unwanted injections
A page may disable PrimeVue injection only while it contains no standard product controls or surfaces that PrimeVue provides. A canvas/SVG/chart-only page is valid. Once it gains a button, input, form, table, dialog, menu, tag, tooltip, or feedback control, use PrimeVue and keep the injection enabled; framework choice alone is not an omission reason.
{
"wippy": {
"proxy": {
"injections": {
"css": {
"primevue": false,
"themeConfig": false
}
}
}
}
}
With both disabled the page still receives customCSS, cssVariables, and iframe.css (themed scrollbar styling) unless those are also turned off. The proxy API, state relay, and WebSocket bridge are unaffected by CSS flags.
Web Components: facade custom CSS + hostCssKeys
Web components do not go through the iframe injection pipeline. Two channels bring the theme into a component's shadow root:
- Configured variables + facade custom CSS.
@wippy-fe/webcomponent-coreenumerates every effective global/children/page custom-property name, including names under@light/@dark, and installs a generic inheritance bridge after platform theme defaults. It then installs composed global + childrencustomCSSas the final layer.customCss: falsedisables only the selector-rule layer; it does not disable configured-variable propagation. - Platform CSS assets (
hostCssKeys).theme-config.css, PrimeVue, markdown, and iframe/scrollbar styles are static bundle assets, not the facade's configured CSS. A component requests the ones it needs by URL throughwippyConfig.hostCssKeys(or fetches them ad hoc withloadCss()from@wippy-fe/proxy), and the runtime injects them into the shadow root.
static get wippyConfig() {
return {
hostCssKeys: ['themeConfigUrl', 'primeVueCssUrl'] as const,
}
}
Use declarative hostCssKeys for normal component authoring. loadCss() is an integration escape hatch; never rewrite a mounted shadow tree with shadowRoot.innerHTML.
Available hostCss keys:
| Key | Content | Bundle impact |
|---|---|---|
hostCss.themeConfigUrl |
CSS variables (--p-primary-*, light + dark) |
Small |
hostCss.primeVueCssUrl |
PrimeVue components + Tailwind utilities | Large |
hostCss.markdownCssUrl |
.data-body markdown rendering styles |
Small |
hostCss.iframeCssUrl |
Scrollbar styling using --p-surface-* |
Tiny |
hostCss.preflightCssUrl |
Tailwind/PrimeVue preflight base reset (normalize/reset) | Small |
A web component that wants host-faithful rendering may need to fetch hostCss.preflightCssUrl with loadCss() and insert the returned text with injectInlineCss(shadow, css), because the host's base preflight reset does not cross the shadow boundary.
For guidance on which keys to request and when — including the decision tree for balancing style fidelity against Shadow DOM bundle size — see WC Theming § hostCssKeys decision tree.
AppConfig.theming projection
The facade config exposes three theming scopes: theming.global, theming.host, and theming.children. Before a page receives its child config, the host projects the effective child theme into AppConfig.theming.global. The selected page engine applies that child global scope through its custom-CSS and custom-variable delivery path.
Keys are CSS variable names exactly as they should appear in CSS:
// In the facade configuration or SetConfig PostMessage payload.
theming: {
global: {
cssVariables: {
'--p-primary': 'rgb(220, 38, 38)',
'--p-surface-0': '#0f0f0f',
'--p-content-border-radius': '2px',
}
}
}
For iframe delivery, the compiler normalizes leading --, merges the top-level base with @light / @dark, and emits effective Auto-light, Auto-dark, forced Light, and forced Dark blocks in the iframe's adopted stylesheet. It is variable-agnostic: palette bases, direct shades/aliases, surfaces, typography, host tokens, and application-specific properties follow the same path. The override does not depend on <head> source order — see Override mechanism.
Override mechanism: adopted stylesheets
In iframe delivery, cssVariables and non-@import declarations from
customCSS are not ordinary <head> <style>/<link> elements. The proxy
places them in the iframe document's
adoptedStyleSheets
(constructable stylesheets). Per the CSS cascade, adopted stylesheets order
after document stylesheets regardless of insertion order, so those
declarations win over theme-config.css, primevue.css, iframe.css, and
markdown.css. The proxy extracts @import rules from customCSS into an
ordinary <head> style instead; imports therefore do not receive this adopted-
stylesheet ordering guarantee. Web Fragment delivery uses ordinary <style>
elements in its reflected head.
Between the two iframe adopted layers, non-@import customCSS overrides
cssVariables: the sheets are ordered cssVariables first, then customCSS,
and later adopted sheets have higher priority. If the same --p-* token is set
in both, the non-import customCSS value wins.
Three theming scopes
The facade supports three cssVariables scopes to target different rendering layers:
| Scope key | Injected into | Use case |
|---|---|---|
theming.global |
Host chrome and every child page | Brand colors, primary palette, shared icon sets |
theming.host |
Host chrome only | Sidebar, header, chat, and app-title overrides |
theming.children |
Child pages only | Child-only CSS variables and CSS overrides |
Child pages do not receive theming.host or theming.children as separate scopes. They receive the merged child-facing result as config.theming.global.
Per-page overrides
Individual pages can override variables via window.__WIPPY_CONFIG_OVERRIDES__ (set in the page's registry entry as meta.config_overrides, or in package.json as wippy.configOverrides):
window.__WIPPY_CONFIG_OVERRIDES__ = {
customization: {
cssVariables: {
'--p-primary': '#ff6b00',
},
customCSS: '.my-page-header { border-radius: 12px; }',
},
}
Backend YAML config_overrides.customization is the per-page authoring surface. Its cssVariables and customCSS keys project into frontend theming.global.cssVariables and customCSS before the page receives AppConfig, replacing the inherited child values for that page. Because the override is merged into theming.global, it propagates down the whole nested sub-tree: every child the page embeds — <w-iframe>, <w-artifact>, and html.inject content — is built from the page's already-merged config and inherits the theme, recursively. So a page (or a module shipping several such pages) themes everything beneath it, not just itself.
--wippy-host-* Variables
The host exposes a set of --wippy-host-* CSS variables for customizing Web Host chrome elements — sidebar, chat bubbles, input bar, panel dividers — without touching child-page styles. Override them via customCSS or cssVariables scoped to :root (the variables are already prefixed and are not projected into child pages):
theming: {
host: {
customCSS: `
:root {
--wippy-host-sidebar-width-open: 20rem;
--wippy-host-splitter-color: transparent;
--wippy-host-message-radius: 0.5rem;
--wippy-host-message-user-bg: var(--p-info-100);
--wippy-host-message-agent-bg: var(--p-warn-100);
}
/* Class selectors must be scoped to .wippy-host-app */
.wippy-host-app .chat-message__footer { display: none; }
`
}
}
Layout variables
| Variable | Default | Description |
|---|---|---|
--wippy-host-sidebar-width-open |
16rem |
Sidebar width when expanded |
--wippy-host-sidebar-width-closed |
3.5rem |
Sidebar width when collapsed |
--wippy-host-splitter-width |
1px |
Panel divider line width |
--wippy-host-splitter-hit-area |
10px |
Panel divider drag area |
--wippy-host-splitter-color |
surface-200/600 |
Panel divider color |
--wippy-host-chat-bg |
surface-50/700 |
Chat container background |
--wippy-host-chat-padding-x |
10px |
Message list horizontal padding |
--wippy-host-meta-bar-border-color |
surface-200/600 |
Agent/model bar border |
Message variables
| Variable | Default | Description |
|---|---|---|
--wippy-host-message-bg |
surface-50/700 |
Default message background |
--wippy-host-message-border-color |
surface-200/600 |
Message bubble border |
--wippy-host-message-shadow |
0 1px 2px 0 rgba(...) |
Message bubble shadow |
--wippy-host-message-font-size |
0.875rem |
Message body text size |
--wippy-host-message-radius |
1rem |
Message bubble corners |
--wippy-host-message-padding-x |
1rem |
Message horizontal padding |
--wippy-host-message-padding-y |
0.5rem |
Message vertical padding |
--wippy-host-message-gap |
0.5rem |
Gap between avatar and bubble |
--wippy-host-message-spacing |
1rem |
Vertical spacing between messages |
--wippy-host-message-user-bg |
primary-50 |
User message background |
--wippy-host-message-agent-bg |
yellow-50/surface-800 |
Agent message background |
--wippy-host-tool-bg |
help-50 |
Tool call background |
--wippy-host-tool-border |
help-300 |
Tool call left border |
--wippy-host-avatar-size |
2rem |
Message avatar diameter |
Input variables
| Variable | Default | Description |
|---|---|---|
--wippy-host-input-bg |
surface-50/700 |
Input bar background |
--wippy-host-input-border-color |
surface-200/600 |
Input bar top border |
--wippy-host-input-group-bg |
surface-0/800 |
Input field background |
--wippy-host-input-group-border-color |
surface-300/700 |
Input field border |
--wippy-host-input-group-radius |
0.375rem |
Input field corners |
--wippy-host-input-min-height |
2.5rem |
Textarea initial height |
--wippy-host-input-max-height |
10rem |
Textarea max height |
Prompt variables
| Variable | Default | Description |
|---|---|---|
--wippy-host-prompt-bg |
surface-100/800 |
Prompt suggestion background |
--wippy-host-prompt-border-color |
surface-300/600 |
Prompt suggestion border |
--wippy-host-prompt-radius |
0.5rem |
Prompt suggestion corners |
These variables only affect the host chrome. Child-page styles are unaffected.
See Also
- Theming — CSS token reference, Tailwind mapping, and web component style patterns
- Proxy & Isolation — how the proxy injection pipeline works and what
ProxyConfigcontrols at the protocol level - Render Engines — host CSS reaches both srcdoc iframes and Web Fragment shadow roots