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:


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:

GlobalPurpose
window.chatonUiUI helpers: ensureStyles(), createButton(), createModelPicker(), createComponents()
window.chatonExtensionComponentsPre-initialized component library (same as calling createComponents())
window.chatonHost 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

ClassDescription
.ce-pagePage container. max-width: 720px, centered, padding: 24px.
.ce-page-headerFlex header with title and actions.
.ce-page-title-groupWraps title and description.
.ce-page-titleMain heading. font-size: 20px, font-weight: 600.
.ce-page-descriptionSubtitle 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

ClassDescription
.ce-cardCard container with border and rounded corners.
.ce-card__bodyCard 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

ClassDescription
.ce-section-titleSection heading. font-size: 15px, font-weight: 600.
.ce-section-copyUppercase meta text. font-size: 12px, muted, letter-spacing: 0.04em.

Grid

ClassDescription
.ce-gridCSS grid container. gap: 12px.
.ce-grid--22-column grid.
.ce-grid--33-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

ClassDescription
.ce-fieldField wrapper. Flex column with gap: 4px.
.ce-labelLabel text. font-size: 13px, font-weight: 500.
.ce-helpHelp 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

ClassDescription
.ce-toolbarFlex 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

ClassDescription
.ce-listList container. Flex column.
.ce-list-rowRow item with bottom border. Flex, padding: 10px 0.
.ce-list-row__mainMain content area (flex: 1).
.ce-list-row__contentInner content flex column.
.ce-list-row__titleRow title. font-weight: 500, font-size: 13px, truncated.
.ce-list-row__metaRow 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

ClassDescription
.ce-emptyCentered placeholder text. padding: 24px, muted color.
<div class="ce-empty">No items yet. Create one to get started.</div>

Buttons

ClassDescription
.chaton-ui-buttonBase button. Rounded, bordered.
.chaton-ui-button--primaryPrimary style. Filled background.
.chaton-ui-button--destructiveRed 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

ClassDescription
.chaton-ui-badgeBase badge. Pill-shaped, font-size: 12px.
.chaton-ui-badge--defaultPrimary color background.
.chaton-ui-badge--secondaryMuted background.
.chaton-ui-badge--destructiveRed 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

VariableLight ModeDark ModePurpose
--chaton-ui-backgroundhsl(220 12% 96%)hsl(222 14% 12%)Page background
--chaton-ui-foregroundhsl(222 12% 14%)hsl(210 20% 90%)Primary text
--chaton-ui-cardhsl(0 0% 100%)hsl(222 13% 17%)Card/input background
--chaton-ui-primaryhsl(220 7% 32%)hsl(210 15% 60%)Primary action color
--chaton-ui-primary-foregroundhsl(0 0% 100%)hsl(222 14% 10%)Text on primary
--chaton-ui-mutedhsl(220 10% 92%)hsl(222 12% 22%)Muted background
--chaton-ui-muted-foregroundhsl(220 6% 44%)hsl(215 10% 55%)Muted text
--chaton-ui-accenthsl(220 10% 93%)hsl(222 12% 22%)Accent background
--chaton-ui-accent-foregroundhsl(222 12% 16%)hsl(210 20% 90%)Accent text
--chaton-ui-borderhsl(220 9% 85%)hsl(222 10% 24%)Border color
--chaton-ui-inputhsl(220 9% 85%)hsl(222 10% 24%)Input border
--chaton-ui-ringhsl(220 9% 70%)hsl(215 10% 40%)Focus ring

Component shorthand variables

VariablePurpose
--ce-fgForeground text color
--ce-mutedMuted text color
--ce-borderBorder color
--ce-card-bgCard background
--ce-input-bgInput 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:

MethodDescription
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.html
  • electron/extensions/builtin/automation/index.js

It demonstrates:

  • window.chatonExtensionComponents for all DOM helpers
  • window.chatonUi.createModelPicker() for model selection
  • window.chaton.extensionCall() for cross-extension API calls
  • window.chaton.extensionHostCall() for notifications
  • Split-pane layout with list + detail pattern
  • Dark mode sync
  • Deeplink handling via postMessage

On this page