Extensions UI Library
Chatons injects styles and JavaScript helpers into every extension webview. Use them to build UIs that look native without writing custom CSS.
Related:
- Extensions Tutorial -- Build a working extension
- Extensions API -- Full API reference
- Requirement Sheets -- Inline prerequisite sheets
What Gets Injected
The bridge script runs automatically before your code loads. This works for both hand-written extension pages and packaged entries produced by bundlers such as Vite or React. It provides:
| Global | Purpose |
|---|---|
window.chatonUi | UI helpers: ensureStyles(), createButton(), createModelPicker(), createComponents() |
window.chatonExtensionComponents | Pre-initialized component library (same as calling createComponents()) |
window.chaton | Host API (see Extensions API) |
CSS Classes
Call window.chatonExtensionComponents.ensureStyles() (or window.chatonUi.ensureStyles()) at the top of your script to inject the stylesheet. Then use these classes in your HTML.
Page Layout
| Class | Description |
|---|---|
.ce-page | Page container. max-width: 720px, centered, padding: 24px. |
.ce-page-header | Flex header with title and actions. |
.ce-page-title-group | Wraps title and description. |
.ce-page-title | Main heading. font-size: 20px, font-weight: 600. |
.ce-page-description | Subtitle text. Muted color, font-size: 13px. |
<div class="ce-page">
<div class="ce-page-header">
<div class="ce-page-title-group">
<h1 class="ce-page-title">My Extension</h1>
<p class="ce-page-description">A short description.</p>
</div>
</div>
<!-- content here -->
</div>
Cards
| Class | Description |
|---|---|
.ce-card | Card container with border and rounded corners. |
.ce-card__body | Card body with padding. |
<div class="ce-card">
<div class="ce-card__body">
<h2 class="ce-section-title">Settings</h2>
<!-- card content -->
</div>
</div>
Section Headings
| Class | Description |
|---|---|
.ce-section-title | Section heading. font-size: 15px, font-weight: 600. |
.ce-section-copy | Uppercase meta text. font-size: 12px, muted, letter-spacing: 0.04em. |
Grid
| Class | Description |
|---|---|
.ce-grid | CSS grid container. gap: 12px. |
.ce-grid--2 | 2-column grid. |
.ce-grid--3 | 3-column grid. |
<div class="ce-grid ce-grid--2">
<div class="ce-field"><!-- left column --></div>
<div class="ce-field"><!-- right column --></div>
</div>
Form Fields
| Class | Description |
|---|---|
.ce-field | Field wrapper. Flex column with gap: 4px. |
.ce-label | Label text. font-size: 13px, font-weight: 500. |
.ce-help | Help text. font-size: 12px, muted color. |
Inputs, textareas, and selects inside .ce-field are automatically styled (full width, rounded borders, focus ring).
<div class="ce-field">
<label class="ce-label" for="name">Name</label>
<input id="name" type="text" placeholder="Enter name">
<div class="ce-help">This is displayed in the sidebar.</div>
</div>
Toolbar
| Class | Description |
|---|---|
.ce-toolbar | Flex row for buttons. gap: 8px, wraps. |
<div class="ce-toolbar">
<button class="chaton-ui-button chaton-ui-button--primary">Save</button>
<button class="chaton-ui-button">Cancel</button>
</div>
List Rows
| Class | Description |
|---|---|
.ce-list | List container. Flex column. |
.ce-list-row | Row item with bottom border. Flex, padding: 10px 0. |
.ce-list-row__main | Main content area (flex: 1). |
.ce-list-row__content | Inner content flex column. |
.ce-list-row__title | Row title. font-weight: 500, font-size: 13px, truncated. |
.ce-list-row__meta | Row metadata. font-size: 12px, muted. |
<div class="ce-list">
<div class="ce-list-row">
<div class="ce-list-row__main">
<div class="ce-list-row__content">
<span class="ce-list-row__title">Item Name</span>
<span class="ce-list-row__meta">Last updated 2 hours ago</span>
</div>
</div>
<span class="chaton-ui-badge chaton-ui-badge--default">Active</span>
</div>
</div>
Empty State
| Class | Description |
|---|---|
.ce-empty | Centered placeholder text. padding: 24px, muted color. |
<div class="ce-empty">No items yet. Create one to get started.</div>
Buttons
| Class | Description |
|---|---|
.chaton-ui-button | Base button. Rounded, bordered. |
.chaton-ui-button--primary | Primary style. Filled background. |
.chaton-ui-button--destructive | Red destructive style. |
<button class="chaton-ui-button">Default</button>
<button class="chaton-ui-button chaton-ui-button--primary">Primary</button>
<button class="chaton-ui-button chaton-ui-button--destructive">Delete</button>
Badges
| Class | Description |
|---|---|
.chaton-ui-badge | Base badge. Pill-shaped, font-size: 12px. |
.chaton-ui-badge--default | Primary color background. |
.chaton-ui-badge--secondary | Muted background. |
.chaton-ui-badge--destructive | Red background. |
<span class="chaton-ui-badge chaton-ui-badge--default">Active</span>
<span class="chaton-ui-badge chaton-ui-badge--secondary">Draft</span>
<span class="chaton-ui-badge chaton-ui-badge--destructive">Error</span>
CSS Variables
The injected stylesheet defines these CSS custom properties on :root. They automatically adjust for dark mode via @media (prefers-color-scheme: dark).
Chatons UI variables
| Variable | Light Mode | Dark Mode | Purpose |
|---|---|---|---|
--chaton-ui-background | hsl(220 12% 96%) | hsl(222 14% 12%) | Page background |
--chaton-ui-foreground | hsl(222 12% 14%) | hsl(210 20% 90%) | Primary text |
--chaton-ui-card | hsl(0 0% 100%) | hsl(222 13% 17%) | Card/input background |
--chaton-ui-primary | hsl(220 7% 32%) | hsl(210 15% 60%) | Primary action color |
--chaton-ui-primary-foreground | hsl(0 0% 100%) | hsl(222 14% 10%) | Text on primary |
--chaton-ui-muted | hsl(220 10% 92%) | hsl(222 12% 22%) | Muted background |
--chaton-ui-muted-foreground | hsl(220 6% 44%) | hsl(215 10% 55%) | Muted text |
--chaton-ui-accent | hsl(220 10% 93%) | hsl(222 12% 22%) | Accent background |
--chaton-ui-accent-foreground | hsl(222 12% 16%) | hsl(210 20% 90%) | Accent text |
--chaton-ui-border | hsl(220 9% 85%) | hsl(222 10% 24%) | Border color |
--chaton-ui-input | hsl(220 9% 85%) | hsl(222 10% 24%) | Input border |
--chaton-ui-ring | hsl(220 9% 70%) | hsl(215 10% 40%) | Focus ring |
Component shorthand variables
| Variable | Purpose |
|---|---|
--ce-fg | Foreground text color |
--ce-muted | Muted text color |
--ce-border | Border color |
--ce-card-bg | Card background |
--ce-input-bg | Input background |
Use these in your custom CSS:
.my-custom-element {
color: var(--ce-fg);
border: 1px solid var(--ce-border);
background: var(--ce-card-bg);
}
.my-subtle-text {
color: var(--ce-muted);
font-size: 12px;
}
JavaScript Helpers
window.chatonExtensionComponents
Pre-initialized component library available immediately. No setup needed.
ensureStyles()
Inject the stylesheet if not already present.
window.chatonExtensionComponents.ensureStyles();
Call this at the top of your app.js.
el(tag, className?, text?)
Create a DOM element.
var ui = window.chatonExtensionComponents;
var heading = ui.el('h2', 'ce-section-title', 'My Section');
var container = ui.el('div', 'ce-card');
cls(...classNames)
Join class names (filters out falsy values).
var classes = ui.cls('ce-list-row', isActive && 'ce-list-row--active');
// "ce-list-row ce-list-row--active" or "ce-list-row"
createButton(options)
Create a styled button element.
var btn = ui.createButton({ text: 'Save', variant: 'default' });
// variant: 'default' | 'outline' | 'ghost'
createBadge(options)
Create a styled badge element.
var badge = ui.createBadge({ text: 'Active', variant: 'default' });
// variant: 'default' | 'secondary' | 'outline'
createCard()
Create a card container with body.
var card = ui.createCard();
// card.root -- the .ce-card element
// card.body -- the .ce-card__body element
card.body.appendChild(ui.el('p', '', 'Card content'));
document.body.appendChild(card.root);
createField(options)
Create a labelled form field.
var input = ui.el('input', '');
input.type = 'text';
input.placeholder = 'Enter value';
var field = ui.createField({
label: 'API Key',
input: input,
help: 'Get this from your provider settings.',
});
// Returns a .ce-field wrapper with label, input, and help text
window.chatonUi.createModelPicker(options)
Create a model picker dropdown that mirrors the Chatons model selection behavior.
var picker = window.chatonUi.createModelPicker({
host: document.getElementById('modelPickerContainer'), // Required: mount target
onChange: function (modelKey) {
console.log('Selected:', modelKey); // e.g. "openai/gpt-4o"
},
labels: {
filterPlaceholder: 'Filter models...',
more: 'more',
scopedOnly: 'scoped only',
noScoped: 'No scoped models',
noModels: 'No models',
},
});
// Load models from the host
window.chaton.listPiModels().then(function (res) {
if (res.ok) {
picker.setModels(res.models); // [{ id, provider, key, scoped }]
picker.setSelected('openai/gpt-4o');
}
});
Returned API:
| Method | Description |
|---|---|
setModels(models) | Set the model list. Each model: { id, provider, key, scoped } |
setSelected(modelKey) | Select a model by key (e.g. "openai/gpt-4o") |
getSelected() | Get the currently selected model key |
destroy() | Remove the picker from the DOM |
Behavior:
- Shows scoped (starred) models by default
- Click "more" to reveal all models with a text filter
- Click "scoped only" to go back to starred models only
Dark Mode
The injected stylesheet handles dark mode automatically via @media (prefers-color-scheme: dark). If you use the provided CSS variables and classes, dark mode works out of the box.
If you need to sync with the parent app's dark class (useful for dynamic theme switching):
function syncThemeClass() {
var isDark = false;
try {
isDark = window.parent.document.documentElement.classList.contains('dark');
} catch (e) {}
document.documentElement.classList.toggle('dark', isDark);
}
syncThemeClass();
This is the pattern used by the built-in automation and memory extensions, and by packaged React widgets such as IDE Launcher and TPS Monitor.
Full-Width Layout
Extension main views render in a full-width, full-height container. They are not constrained to the centered conversation column.
This means your extension can build:
- Admin dashboards
- Full-screen configuration pages
- Split-pane layouts (like the automation extension)
- Data tables and catalog views
Use .ce-page (which sets max-width: 720px) for simple form-based views. Skip it for full-width layouts.
Reference Implementation
The built-in automation extension is the primary reference for how to use these helpers in practice:
electron/extensions/builtin/automation/index.htmlelectron/extensions/builtin/automation/index.js
It demonstrates:
window.chatonExtensionComponentsfor all DOM helperswindow.chatonUi.createModelPicker()for model selectionwindow.chaton.extensionCall()for cross-extension API callswindow.chaton.extensionHostCall()for notifications- Split-pane layout with list + detail pattern
- Dark mode sync
- Deeplink handling via
postMessage