Skip to content

Strict styling requirements ​

Extensions must follow the ISA Warden design system as closely as possible.

14.0 Extension styling platform ​

ISA Warden now exposes a small extension styling platform for iframe content:

Recommended extension entry setup:

html
<link rel="stylesheet" href="../shared/isa-extension-theme.css" />
<link rel="stylesheet" href="../shared/isa-extension-notifications.css" />
<script type="module">
	import { initHostThemeBridge } from '../shared/isa-extension-theme.js';

	initHostThemeBridge();
</script>

Packaged extensions may copy these shared files into their package if the relative path differs. Keep the filenames and public API unchanged when possible so examples remain portable.

The host sends this theme payload to extension iframes when the iframe loads and when the host theme changes:

json
{
	"theme": "dark",
	"effectiveTheme": "dark",
	"systemPrefersDark": true,
	"designTokenVersion": 1,
	"designTokens": {
		"--isa-extension-panel-bg": "rgb(17 24 34 / 0.96)",
		"--isa-extension-panel-text": "rgb(248 250 252 / 0.97)"
	}
}

Extension code should treat designTokens as the stable styling contract. The token names are extension-facing compatibility names, even when their values are sourced from host-internal CSS variables. Payload values are self-contained: a value may be concrete CSS or reference another --isa-extension-* token, but it must never reference host-private variables such as --brand-* or --color-*. The shared theme helper rejects non-portable values and removes previously applied inline tokens when a later theme payload omits them, allowing the stylesheet fallback for the active theme to take effect again.

Use these starter classes before adding local primitives:

  • isa-extension-shell
  • isa-extension-panel
  • isa-extension-card
  • isa-extension-heading
  • isa-extension-muted
  • isa-extension-button, isa-extension-button--primary, isa-extension-button--danger, isa-extension-icon-button
  • isa-extension-input, isa-extension-textarea, isa-extension-select, isa-extension-label
  • isa-extension-modal-backdrop, isa-extension-modal
  • isa-extension-notice, isa-extension-notice--success, isa-extension-notice--warning, isa-extension-notice--error
  • isa-extension-badge
  • isa-extension-table
  • isa-extension-prose

Extension-local CSS should consume --isa-extension-* variables for shell, panel, card, text, border, focus, action, radius, and shadow roles. Domain-specific colors may still be added, but they must be defined as local tokens and remain isolated to domain-specific UI.

14.1 Design-system-first rule ​

Before adding local styling, extension authors must:

  1. reuse an existing semantic class from chat-ui-3/src/app.css
  2. reuse an existing token from chat-ui-3/src/app.css
  3. extend a nearby semantic pattern
  4. only then add extension-local styling

Custom styling is the exception, not the default.

14.2 Required surface rules ​

Use these host surface patterns where possible:

Do not create a separate panel/card visual system when these fit the use case.

14.3 Border radius rules ​

Use host radius tokens and semantic utilities:

Rules:

  • panels, cards, and modal shells must use panel radius semantics
  • inputs, buttons, icon buttons, selectors, and similar controls must use control radius semantics
  • hardcoded radius values for standard components are forbidden unless a documented exception exists

14.4 Button rules ​

Use shared button styles where applicable:

Do not create a second generic button system for standard actions.

14.5 Form and control rules ​

Use shared control patterns, including:

Inputs, selects, and textareas must align with host focus, disabled, error, and success treatment.

14.6 Selector and flyout rules ​

Use the selector system where the control is selector-like:

Do not create a separate dropdown/flyout visual system when the selector system fits.

14.7 Modal rules ​

Use shared modal shells and surfaces where applicable:

Do not hand-roll modal shells when host modal primitives fit the use case.

14.8 Color rules ​

Rules:

  • use semantic tokens and shared class colors from chat-ui-3/src/app.css
  • do not hardcode standard text, surface, border, or button colors when shared tokens/classes already cover the need
  • brand-specific colors are only allowed where there is a genuine product or domain reason and accessibility remains valid

14.9 Dark and light mode requirements ​

Every extension UI must support both themes.

Verify:

  • text contrast
  • icon contrast
  • border visibility
  • hover and focus states
  • selected and disabled states

14.10 Prohibited styling patterns ​

Extensions must not:

  • create a parallel generic button system when host buttons fit
  • create a separate dropdown/flyout system when selector primitives fit
  • hardcode standard border radii
  • hardcode standard surface/text colors without need
  • create a modal shell from scratch when shared modal primitives fit
  • assume only one theme exists

14.11 Notifications and transient messages ​

Every extension must use the framework-neutral shared notification contract:

Import the stylesheet after isa-extension-theme.css and mount exactly one notification centre at the extension application root. A modal, route, panel, or individual workflow must publish to that root-owned centre; it must never mount a second local stack. The shared stylesheet fixes the stack to the bottom-right, accounts for safe-area insets, switches to a responsive mobile inset, bounds and scrolls long stacks, and supplies semantic tone, countdown, focus, and reduced-motion styles.

The shared JavaScript module exports:

js
MAX_NOTIFICATION_COUNT;
NOTIFICATION_TIMEOUT_MS;
NOTIFICATION_EXIT_DURATION_MS;
NOTIFICATION_DISMISS_DELAY_MS;
getNotificationTone(kind, message);
appendNotification(notifications, candidate, options);
dismissNotification(notifications, notificationId);
dismissNotificationsByKey(notifications, notificationKey);
normalizeExtensionError(error, { fallback });

Notification renderers use this complete, framework-neutral record:

js
const notification = {
	id,
	key,
	tone,
	title,
	message,
	detail,
	action,
	actionLabel,
	createdAt,
	timeoutStartedAt
};

When id, createdAt, or timeoutStartedAt is omitted, appendNotification generates it. title remains empty when omitted so the renderer can supply a localized title for the semantic tone.

appendNotification caps the list at 20 by default, suppresses an adjacent unkeyed duplicate, and updates a keyed notification in place. Use a stable key for durable operational conditions such as a remote project update. Restart the dismiss timer when a keyed record receives a new timeoutStartedAt. Begin exit styling after NOTIFICATION_DISMISS_DELAY_MS, then remove the record after NOTIFICATION_EXIT_DURATION_MS.

Use the shared classes as the renderer contract:

  • .isa-extension-notification-center
  • .isa-extension-notification-header
  • .isa-extension-notification-clear
  • .isa-extension-notification-list
  • .isa-extension-notification with data-tone="success|warning|error|info"
  • .isa-extension-notification-icon
  • .isa-extension-notification-content
  • .isa-extension-notification-title
  • .isa-extension-notification-message
  • .isa-extension-notification-detail
  • .isa-extension-notification-action
  • .isa-extension-notification-dismiss
  • .isa-extension-notification-timer

Set data-transition-managed="true" when the renderer supplies its own enter and exit transition; otherwise the shared stylesheet supplies the default enter animation.

Set role="alert" and an assertive live region only for errors. Use role="status" and polite announcements for all other tones. Notifications must not steal focus. The clear control clears the complete stack; dismiss controls remove one record. Recovery actions stay inside the relevant notification and are executed by the extension renderer.

Call normalizeExtensionError before presenting caught host or bridge errors. It recursively unwraps JSON serialized into Error.message and returns only { code, message, retryable }. Translate known error codes into concise, extension-local copy and pass translated fallback copy:

js
const normalized = normalizeExtensionError(error, {
	fallback: translate('errors.unexpected')
});
const message = translateKnownErrorCode(normalized.code) || normalized.message;
notifications = appendNotification(notifications, {
	tone: 'error',
	title: translate('notifications.error'),
	message
});

Never display a serialized error object as the notification message. Unknown technical detail may be placed in detail when it helps recovery, but the primary message must remain readable.

Keep field validation, typed-confirmation errors, loading/progress, empty states, and durable sync or storage-health status next to the control or surface they describe. These are contextual states, not transient notifications. A general operation failure may also create a notification, but do not duplicate the same text both inline and in the notification centre.

Operational outcomes such as save, delete, import, export, archive, restore, recording, transcription, and analysis success or failure belong in the root notification centre. Contextual state that remains true without a recent operation belongs inline. Do not show the same message both inline and as a notification.

Use only shared --isa-extension-* tokens for notification styling. The implementation in isa-match-rsx-to-quote remains the Svelte reference, but new extensions must consume the shared framework-neutral files instead of copying its local implementation.

14.12 Host styling with mod ​

The iframe design-token contract remains the required styling path for normal extensions. Without the explicit mod permission, extension CSS is isolated to its iframe and must not style or add UI to the parent application.

A mod may apply application-wide styling only from its separately declared modEntry after the host warning has been accepted. Prefer context.appendStyle(cssText) and context.appendRoot('appShell' | 'extensionToolbar') so contributions are tagged, tracked, and removed on disposal. Use the named mount points rather than locating incidental component internals, and reuse host semantic classes and tokens when the mounted control can do so.

Every direct host mutation must have explicit cleanup through context.onDispose or the function returned from activate. Event listeners should use context.addEventListener, and helper-created styles and roots must not be moved out of their tracked owner. Test cleanup after close and reload as well as light, dark, hover, focus, active, and disabled states.

The permission warning is not styling guidance and does not make host CSS stable or sandboxed. Avoid broad resets, fragile selectors, inaccessible contrast, or changes that obscure the host-owned active-mod indicator and dialogs.

ISA Warden extension specification