Rendering Modes
The dev server supports four rendering modes that control how your demos are presented: light mode (full development UI), shadow mode (demos in shadow DOM ), iframe mode (full isolation with knobs), and chromeless mode (minimal standalone pages). Choose the mode that fits your workflow–light for interactive development with knobs and live monitoring, shadow for testing CSS encapsulation, iframe for full style and tag name isolation, or chromeless for automated testing and clean demo sharing.
All modes include live reload with smart dependency tracking and support query parameter overrides for quick testing. The rendering mode affects UI presentation and error handling but preserves core functionality like TypeScript transforms, import map injection, and file watching across all modes.
Light Mode (Default)
Light mode provides the full development experience with a PatternFly-based UI that includes sidebar navigation, header with theme toggle and debug info, interactive knobs for attributes and properties, real-time event monitoring, server logs with filtering, manifest browser, and visual live reload status. Error overlays appear for transform failures, making debugging straightforward. This is the standard mode for component development.
Use cem serve (light mode is the default) or configure it explicitly in .config/cem.yaml:
serve:
demos:
rendering: lightIf the rendering option is omitted or empty, demos default to light mode.
Shadow Mode
Shadow mode provides the same full UI as light mode but renders demo content inside a shadow root, letting you test components in encapsulated contexts. Use this when verifying CSS encapsulation, testing :host selectors and shadow piercing, or debugging shadow DOM-specific behaviors.
Start the server with cem serve --rendering=shadow or configure it in .config/cem.yaml:
serve:
demos:
rendering: shadowIframe Mode
Iframe mode provides the same full UI as light mode–sidebar navigation, knobs, manifest browser–but renders demo content inside an <iframe> for complete isolation. The demo runs in a separate document, so its styles, custom element registrations, and DOM queries cannot leak into or be affected by the dev server chrome. Knobs still work: changes bridge to the iframe via postMessage.
Use iframe mode when scoped custom element registries are not available and you need to prevent tag name collisions between demo components and the dev server UI, ensure demo querySelector calls cannot reach outside the demo boundary, or verify that components render correctly without any inherited styles.
Start the server with cem serve --rendering=iframe or configure it in .config/cem.yaml:
serve:
demos:
rendering: iframeIframe mode loads each demo as a chromeless page inside the iframe, so live reload, TypeScript/CSS transforms, and import map injection all work as expected. The tradeoff is slightly slower initial load compared to light or shadow mode due to the separate document.
Chromeless Mode
Chromeless mode strips away all dev server UI, serving demos as clean standalone pages ideal for automated testing with Playwright or Puppeteer, embedding in documentation sites, sharing clean demo URLs, or capturing screenshots. Core functionality remains—live reload, file watching, TypeScript/CSS transforms, and import map injection—but errors log to the console instead of showing overlays, and there’s no visual chrome, knobs panel, or connection status indicators.
Start with cem serve --rendering=chromeless or configure it in .config/cem.yaml:
serve:
demos:
rendering: chromelessOverride per-demo with the ?rendering=chromeless query parameter.
Playwright Integration
Configure Playwright to use chromeless mode for clean component testing without UI interference:
// playwright.config.js
export default {
webServer: {
command: 'cem serve --rendering=chromeless',
port: 8000,
reuseExistingServer: !process.env.CI,
},
use: { baseURL: 'http://localhost:8000' },
};
// tests/button.spec.js
import { test, expect } from '@playwright/test';
test('button component', async ({ page }) => {
await page.goto('/elements/my-button/demo/');
const button = page.locator('my-button');
await expect(button).toBeVisible();
await button.click();
await expect(button).toHaveAttribute('aria-pressed', 'true');
});Query Parameter Override
Override the default rendering mode for any demo with the ?rendering= query parameter—useful for testing different contexts without restarting the server, sharing clean demo links, or quick mode comparisons:
http://localhost:8000/elements/button/demo/?rendering=chromeless
http://localhost:8000/elements/button/demo/?rendering=shadow
http://localhost:8000/elements/button/demo/?rendering=iframe
http://localhost:8000/elements/button/demo/?rendering=lightValid values are light, shadow, iframe, and chromeless. Invalid values are ignored and the configured default is used.
Backward Compatibility
The legacy ?shadow=true query parameter is still supported and will override to shadow mode:
http://localhost:8000/elements/button/demo/?shadow=trueLive Reload and Error Handling
All modes use WebSocket -based live reload with smart dependency tracking that only triggers reloads when relevant imported files change. Light and shadow modes show visual connection status with reconnection modals and toasts, while chromeless mode reloads silently with console-only logging.
Error handling adapts to each mode: light and shadow display full-screen overlays for TypeScript/CSS transform errors with visual indicators and detailed logs in the UI, while chromeless logs errors to the browser console with a [CEM] prefix to maintain clean demo presentation even during errors.
Use Cases by Mode
Light DOM (default):
- General component testing with full dev UI
- Demos that rely on global styles
- Testing how components integrate with the parent page
- Interactive development with knobs and event monitoring
Shadow DOM:
- Testing encapsulation behavior
- Verifying CSS custom properties penetrate shadow boundaries
- Testing
::part()and::slotted()selectors - Ensuring styles don’t leak in or out
Iframe:
- Full style and tag name isolation without scoped registries
- Preventing demo
querySelectorfrom reaching dev server UI - Testing components free of inherited styles
- Interactive development with knobs in an isolated context
Chromeless:
- Automated testing with Playwright/Puppeteer
- Embedding demos in documentation sites
- Sharing clean demo URLs without dev UI
- Capturing screenshots for documentation
Configuration Examples
Default all demos to shadow mode:
serve:
demos:
rendering: shadowThen override specific demos back to light mode when needed:
http://localhost:8000/elements/integration-test/demo/?rendering=lightUse chromeless for CI testing:
# .config/cem.ci.yaml
serve:
demos:
rendering: chromeless# In CI pipeline
cem serve --config .config/cem.ci.yaml