Skip to content

Widget Runtime

Understand how custom widgets are loaded, initialized, and managed by the platform. This page covers the runtime model that applies to all widgets regardless of framework — React, Vue, vanilla JavaScript, or anything else.

How Widgets Run

Every widget runs inside a Shadow DOM that the platform creates for you. The Shadow DOM isolates your widget's styles and markup from the host page. At runtime, the platform also creates an SDK instance that gives your code access to props, design tokens, events, and the shadow root where your widget renders.

The init(sdk) Contract

Every widget must export an init function (or a default export). The platform calls this function with an SDK instance after the widget connects to the DOM:

javascript
export async function init(sdk) {
  await sdk.whenReady()
  // Mount your UI into sdk.getContainer()
}

// OR default export
export default async function (sdk) {
  await sdk.whenReady()
  // Mount your UI into sdk.getContainer()
}

Your widget is an ES module. The platform loads it via a <script type="module"> tag inside the widget's shadow DOM.

Widget Lifecycle

Initialization

When a widget appears on a page, the platform runs through this sequence:

Once alive, the widget receives propsChanged, designTokensChanged, and custom events until it is removed.

Teardown

When the widget is removed from the page, the SDK emits a destroy event. Use this to clean up your UI framework, cancel network requests, clear timers, and remove event listeners.

Example cleanup:

javascript
export function init(sdk) {
  const interval = setInterval(() => fetchData(), 30000)

  sdk.on('destroy', () => {
    clearInterval(interval)
  })
}

Props

Props come from the widget's configuration (set via the No-Code Builder or Widget Definition Reference). Read them with getProps() and listen for changes with the propsChanged event:

javascript
const props = sdk.getProps()
console.log(props.title)

sdk.on('propsChanged', (newProps) => {
  console.log('Config updated:', newProps)
})

Design Tokens

Design tokens are CSS custom properties that reflect the community's branding (colors, fonts, etc.). They are injected into your shadow DOM automatically, so you can use them in CSS:

css
h1 {
  color: var(--config--main-color-brand, #2563eb);
}

.card {
  background: var(--config--main-color-background, #ffffff);
  border-color: var(--config--main-color-border, #e5e7eb);
}

Always provide fallback values (the second argument to var()) so your widget renders correctly outside a community context.

You can also read tokens programmatically with getDesignTokens() and listen for changes with the designTokensChanged event:

javascript
const tokens = sdk.getDesignTokens()
console.log(tokens['config--main-color-brand'])

sdk.on('designTokensChanged', (newTokens) => {
  console.log('Branding updated:', newTokens)
})

Custom Events

Widgets can emit and listen for custom events to communicate with the platform or other widgets:

javascript
sdk.emit('taskCompleted', { taskId: 42 })

const unsubscribe = sdk.on('taskCompleted', (data) => {
  console.log('Task completed:', data)
})

The on() method returns an unsubscribe function — call it when you no longer need the listener.

Best Practices

Shadow DOM awareness

  • Mount framework apps with sdk.getContainer() — pass it directly to createRoot() (React) or createApp().mount() (Vue). For finer control, use sdk.$('#root') to target a specific element inside the shadow root.
  • Query elements with sdk.$() and sdk.$$() for scoped shadow root access. These are shorthands for shadowRoot.querySelector() and shadowRoot.querySelectorAll().
  • Styles are scoped to the shadow DOM automatically — use this to your advantage.

Styling

  • Prefer CSS custom properties (var(--config--...)) for branding, so your widget adapts to the community's theme.
  • Use the :host selector to style the widget container itself.
  • Always provide fallback values for design tokens: var(--config--main-color-brand, #2563eb).

Bundle size

  • Tree-shake aggressively. Only import what you need.
  • Lazy-load heavy dependencies with dynamic import().

Cleanup

  • Always listen for destroy and tear down your UI. Failing to do so leaks framework state and event listeners.
  • Unsubscribe from SDK events when they are no longer needed (the on() return value is the unsubscribe function).
  • Clear timers and cancel requests in your destroy handler.

Next Steps

Gainsight CC Developer Portal