Skip to main content
When using Daily Prebuilt (iframe mode), you can customize its appearance through themes, CSS injection, layout configuration, custom sidebar integrations, and custom tray buttons.
Theme and CSS customization apply to Daily Prebuilt (iframe-based calls). Call Object mode gives you full control over UI and does not use these APIs.

Themes

DailyThemeConfig

A theme is either a single set of colors (applied regardless of the user’s OS preference) or separate light and dark variants:
type DailyThemeConfig =
  | { colors: DailyThemeColors }  // single theme
  | { light: DailyTheme; dark: DailyTheme };  // adaptive theme

type DailyTheme = { colors: DailyThemeColors };

DailyThemeColors

All color values are optional hex strings (e.g. '#3B82F6'):
accent
string
Primary action color. Used for buttons, active states, and keyboard focus rings.
accentText
string
Text color rendered on top of accent backgrounds.
background
string
Main background color of the call UI.
backgroundAccent
string
Background color for highlighted or elevated elements (e.g. toolbars).
baseText
string
Default text color, as rendered on background or backgroundAccent.
border
string
Default border color for bordered elements.
mainAreaBg
string
Background color of the main video call area.
mainAreaBgAccent
string
Background color for individual video tiles in the main area.
mainAreaText
string
Text color for text rendered inside the main call area (e.g. participant names).
supportiveText
string
Text color for secondary, less-emphasized text.

setTheme() and theme()

// Apply a theme
const applied = await call.setTheme({
  colors: {
    accent: '#6366F1',
    accentText: '#FFFFFF',
    background: '#0F172A',
    backgroundAccent: '#1E293B',
    baseText: '#F1F5F9',
    border: '#334155',
    mainAreaBg: '#0F172A',
    mainAreaBgAccent: '#1E293B',
    mainAreaText: '#F1F5F9',
    supportiveText: '#94A3B8',
  },
});

// Read the current theme
const current = call.theme();

Setting a theme at construction time

Pass theme directly to createFrame() or wrap() to apply it before the call loads:
const call = Daily.createFrame({
  theme: {
    light: {
      colors: {
        accent: '#3B82F6',
        background: '#FFFFFF',
        mainAreaBg: '#F8FAFC',
      },
    },
    dark: {
      colors: {
        accent: '#60A5FA',
        background: '#0F172A',
        mainAreaBg: '#1E293B',
      },
    },
  },
});

theme-updated event

call.on('theme-updated', ({ theme }) => {
  console.log('Theme changed:', theme);
});

Custom CSS

Inject CSS into Prebuilt for styling beyond what the theme colors allow.

At construction time

const call = Daily.createFrame({
  bodyClass: 'my-call-theme',
  cssFile: 'https://example.com/daily-overrides.css',
  cssText: `
    .daily-video-tile { border-radius: 12px; }
    .daily-controls { background: transparent; }
  `,
});

After construction (iframe mode only)

call.loadCss({
  bodyClass: 'updated-theme',
  cssFile: 'https://example.com/updated.css',
  cssText: '.my-class { color: red; }',
});
bodyClass
string
CSS class name(s) added to the <body> element inside the Prebuilt iframe.
cssFile
string
URL of an external stylesheet to load inside the iframe.
cssText
string
Inline CSS string injected as a <style> tag inside the iframe.

Layout configuration

Control grid tile counts via DailyLayoutConfig:
const call = Daily.createFrame({
  layoutConfig: {
    grid: {
      maxTilesPerPage: 9,
      minTilesPerPage: 1,
    },
  },
});

Custom integrations

Embed external web apps as either a sidebar panel or a main area overlay inside Prebuilt.

Defining integrations

call.setCustomIntegrations({
  'my-app': {
    label: 'My App',
    location: 'sidebar',          // 'sidebar' | 'main'
    src: 'https://app.example.com/embed',
    iconURL: 'https://app.example.com/icon.png',
    controlledBy: 'owners',       // '*' | 'owners' | string[]
    shared: true,                 // sync state with all participants
    allow: 'camera; microphone',
    sandbox: 'allow-scripts allow-same-origin',
    loading: 'lazy',
  },
});

// Read currently registered integrations
const integrations = call.customIntegrations();
label
string
required
Display name shown in Prebuilt’s UI.
location
'main' | 'sidebar'
required
Where the integration iframe is rendered.
src
string
Source URL for the embedded iframe.
srcdoc
string
Inline HTML content for the iframe (alternative to src).
controlledBy
'*' | 'owners' | string[]
Who can start and stop this integration. Default: '*' (all participants).
shared
boolean | 'owners' | string[]
When truthy, starting/stopping the integration is synchronized for all or a subset of participants.
allow
string
Feature policy for the iframe (maps to the HTML allow attribute).
sandbox
string
Sandbox restrictions for the iframe.
loading
'eager' | 'lazy'
Whether to load the integration eagerly or lazily. Default: 'lazy'.

Starting and stopping integrations

// Start one or more integrations
call.startCustomIntegrations('my-app');
call.startCustomIntegrations(['my-app', 'another-app']);

// Stop integrations
call.stopCustomIntegrations('my-app');

Custom tray buttons

Add custom buttons to Prebuilt’s control bar.

Defining tray buttons

call.updateCustomTrayButtons({
  'raise-hand': {
    iconPath: 'https://example.com/icons/hand.svg',
    iconPathDarkMode: 'https://example.com/icons/hand-dark.svg',
    label: 'Raise hand',
    tooltip: 'Raise or lower your hand',
    visualState: 'default',
  },
});

// Read current tray buttons
const buttons = call.customTrayButtons();
iconPath
string
required
URL to an SVG or image file used as the button icon in light mode.
iconPathDarkMode
string
URL to an alternative icon for dark mode. Falls back to iconPath if not set.
label
string
required
Accessible label for the button.
tooltip
string
required
Tooltip text shown on hover.
visualState
'default' | 'sidebar-open' | 'active'
Current visual state of the button. Use 'active' to show a pressed/toggled style, 'sidebar-open' to indicate an open sidebar.

custom-button-click event

call.on('custom-button-click', ({ button_id }) => {
  if (button_id === 'raise-hand') {
    toggleHandRaise();

    // Update the button's visual state
    const currentButtons = call.customTrayButtons();
    const isActive = currentButtons['raise-hand'].visualState === 'active';
    call.updateCustomTrayButtons({
      'raise-hand': {
        ...currentButtons['raise-hand'],
        visualState: isActive ? 'default' : 'active',
      },
    });
  }
});

Example: light and dark theme

const call = Daily.createFrame({
  theme: {
    light: {
      colors: {
        accent: '#3B82F6',
        accentText: '#FFFFFF',
        background: '#FFFFFF',
        backgroundAccent: '#F1F5F9',
        baseText: '#0F172A',
        border: '#E2E8F0',
        mainAreaBg: '#F8FAFC',
        mainAreaBgAccent: '#E2E8F0',
        mainAreaText: '#0F172A',
        supportiveText: '#64748B',
      },
    },
    dark: {
      colors: {
        accent: '#60A5FA',
        accentText: '#0F172A',
        background: '#0F172A',
        backgroundAccent: '#1E293B',
        baseText: '#F1F5F9',
        border: '#334155',
        mainAreaBg: '#020617',
        mainAreaBgAccent: '#0F172A',
        mainAreaText: '#F1F5F9',
        supportiveText: '#94A3B8',
      },
    },
  },
});
The Prebuilt UI will automatically switch between light and dark variants based on the user’s operating system preference (prefers-color-scheme).