Skip to content

Global Settings System

RTC Agent provides a complete global settings system supporting four categories: appearance, chat, files, and notifications. All settings are persisted to localStorage with multi-tab synchronization.

Side drawer panel wrapping rtc-settings-layout, providing overlay and keyboard navigation:

<rtc-settings-panel theme="system"></rtc-settings-panel>

Attributes:

AttributeTypeDefaultDescription
theme'light' | 'dark' | 'system''system'Theme mode

Events:

EventDescription
closeTriggered when the panel is closed

Keyboard Support:

KeyBehavior
EscClose panel
Click overlayClose panel

Two-column layout component: left column for category navigation + right column for settings content.

<rtc-settings-layout theme="system"></rtc-settings-layout>

Attributes:

AttributeTypeDefaultDescription
theme'light' | 'dark' | 'system''system'Theme mode

Accessibility:

  • Uses delegatesFocus: true for keyboard focus delegation
  • Uses role="tabpanel" and aria-labelledby to associate navigation
  • Uses aria-live="polite" for screen reader real-time feedback

Left-side category navigation component:

<rtc-settings-nav .active=${'appearance'}></rtc-settings-nav>

Properties:

PropertyTypeDefaultDescription
activeSettingsCategory'appearance'Currently selected category

Events:

EventDetailDescription
settings-nav-change{ category: SettingsCategory }Category switch

Categories:

CategoryValueIcon
Appearanceappearance⚙️
Chatchat💬
Filesfiles📁
Notificationsnotifications☑️
Accountaccount👤
Aboutabout📝
interface SettingsState {
appearance: {
theme: 'light' | 'dark' | 'system';
fontSize: number; // 12-24px
};
chat: {
sendShortcut: 'Enter' | 'Ctrl+Enter';
density: 'compact' | 'comfortable';
};
files: {
autoSave: boolean;
defaultViewMode: 'edit' | 'preview' | 'split';
};
notifications: {
soundEnabled: boolean;
toastEnabled: boolean;
};
}
interface SettingsActions {
updateAppearance(partial: Partial<SettingsState['appearance']>): void;
updateChat(partial: Partial<SettingsState['chat']>): void;
updateFiles(partial: Partial<SettingsState['files']>): void;
updateNotifications(partial: Partial<SettingsState['notifications']>): void;
resetAll(): void;
}
interface SettingsContextValue {
state: SettingsState;
actions: SettingsActions;
}
const DEFAULT_SETTINGS_STATE: SettingsState = {
appearance: { theme: 'system', fontSize: 14 },
chat: { sendShortcut: 'Enter', density: 'comfortable' },
files: { autoSave: true, defaultViewMode: 'split' },
notifications: { soundEnabled: true, toastEnabled: true },
};
import { consume } from '@lit/context';
import { SettingsContext, type SettingsContextValue } from '../contexts/settings.js';
@consume({ context: SettingsContext, subscribe: true })
@property({ attribute: false })
private _settingsCtx: SettingsContextValue;
// Read settings
const theme = this._settingsCtx.state.appearance.theme;
const fontSize = this._settingsCtx.state.appearance.fontSize;
// Update settings
this._settingsCtx.actions.updateAppearance({ fontSize: 16 });
this._settingsCtx.actions.updateChat({ sendShortcut: 'Ctrl+Enter' });

Settings are saved to localStorage with key rtc_settings:

// Read
const raw = localStorage.getItem(STORAGE_KEYS.settings);
const saved = JSON.parse(raw);
// Write
localStorage.setItem(STORAGE_KEYS.settings, JSON.stringify(this._state));

SettingsController automatically handles DOM side effects:

SettingDOM Side Effect
appearance.themeUpdates rtc-agent element’s theme attribute
appearance.fontSizeSets --rtc-font-size-user CSS variable on document.documentElement
// _applyTheme()
const rtcAgent = this.host.closest('rtc-agent') || this.host;
if ('theme' in rtcAgent) {
rtcAgent.theme = theme;
}
// _applyFontSize()
document.documentElement.style.setProperty(
'--rtc-font-size-user',
`${clamped}px`
);

Listens for storage events to synchronize across tabs:

// SettingsController.hostConnected()
window.addEventListener('storage', this._storageListener);
// SettingsController._handleStorageChange()
private _handleStorageChange() {
const oldState = this._state;
this._restore();
this._applyDiff(oldState, this._state);
this.host.requestUpdate();
}

Monitors system theme changes, automatically following when theme: 'system':

this._themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
this._themeMediaQuery.addEventListener('change', () => {
if (this._state.appearance.theme === 'system') {
this._applyTheme();
}
});
SettingTypeDefaultDescription
theme'light' | 'dark' | 'system''system'Theme mode
fontSizenumber14Base font size (12-24px)
SettingTypeDefaultDescription
sendShortcut'Enter' | 'Ctrl+Enter''Enter'Send message shortcut
density'compact' | 'comfortable''comfortable'Message density

sendShortcut:

ValueBehavior
EnterPress Enter to send, Shift+Enter for newline
Ctrl+EnterPress Ctrl/Cmd+Enter to send, Enter for newline

density:

ValueMessage Spacing
compactCompact (smaller spacing)
comfortableComfortable (default spacing)
SettingTypeDefaultDescription
autoSavebooleantrueAuto-save when editing
defaultViewMode'edit' | 'preview' | 'split''split'Default view mode

defaultViewMode:

ValueDescription
editEditor only
previewPreview only
splitEditor + Preview split
SettingTypeDefaultDescription
soundEnabledbooleantrueEnable sound notifications
toastEnabledbooleantrueEnable Toast notifications
const agent = document.querySelector('rtc-agent');
agent.addEventListener('rtc-agent-ready', () => {
// Get SettingsController (via Context)
// Note: Typically operated by users in the settings panel
// Listen for setting changes (by subscribing to Context)
});
import { localized, msg } from '@lit/localize';
import { consume } from '@lit/context';
import { SettingsContext, type SettingsContextValue } from '../contexts/settings.js';
@localized()
@customElement('my-chat-component')
export class MyChatComponent extends LitElement {
@consume({ context: SettingsContext, subscribe: true })
@state()
private _settingsCtx: SettingsContextValue;
render() {
const density = this._settingsCtx.state.chat.density;
const shortcut = this._settingsCtx.state.chat.sendShortcut;
return html`
<div class="chat ${density}">
<!-- Adjust message spacing based on density -->
<p>Send shortcut: ${shortcut}</p>
</div>
`;
}
}
this._settingsCtx.actions.resetAll();