Custom sandbox views and themes
Use ui to display custom HTML/CSS in your own sandbox when declarative forms are insufficient, in a view or on a page in Fluxer’s settings. There is one visible custom view per plugin.
Create a view
Section titled “Create a view”This api-example builds DOM only inside the plugin frame. The lifecycle cleanup array must be drained from stop.
const main = document.createElement("main");const title = document.createElement("h1");title.textContent = "My plugin";const note = document.createElement("p");note.textContent = "This interface lives in the plugin sandbox.";main.append(title, note);document.body.append(main);const style = document.createElement("style");style.textContent = "body { padding: 24px; color: var(--text-primary, #eee); font: 16px var(--font-sans, system-ui); }";document.head.append(style);const applyTheme = (tokens: Record<string, string>) => { for (const [name, value] of Object.entries(tokens)) { document.documentElement.style.setProperty(name, value); }};applyTheme(await api.ui.theme());cleanup.push(() => { main.remove(); style.remove();});cleanup.push(await api.ui.onThemeChange(applyTheme));cleanup.push(await api.ui.openView({ title: "My plugin", kind: "dialog" }));For persistent presentation choose kind panel. Escape, the host Close button, or the returned disposer dismisses the view. Reopening uses the same instance and frame.
Settings pages
Section titled “Settings pages”ui.registerSettingsPage(page, handlers?) adds an entry under FluxPlugs in Fluxer’s settings, after Plugins and Settings. Plugin pages are sorted by title, and the entry’s tooltip names your plugin. While the user has the page open, your frame, the same document a view uses, is drawn over the page’s whole content area, from below Fluxer’s header to the bottom of the settings window.
const render = () => { const main = document.createElement("main"); const heading = document.createElement("h1"); heading.textContent = "My plugin"; main.append(heading); document.body.replaceChildren(main); document.body.style.cssText = "margin: 0; padding: 16px; color: var(--text-primary, #eee); font: 14px var(--font-sans, system-ui);";};cleanup.push( await api.ui.registerSettingsPage( { title: "My plugin" }, { onShow: render, onHide: () => api.logger.debug("Settings page left"), }, ),);- title is 1 to 40 characters after trimming, without control or text-direction characters.
- onShow runs each time the user opens the page; draw it then. onHide runs when they leave it: another tab, settings closed, or the page removed.
- A plugin has one page. Registering another fails with settings_page_exists until the first is disposed.
- There is one frame per plugin. While the page is shown, ui.openView fails with view_busy; opening the page closes an open view. Native dialogs from ui.openDialog still work.
- Set your text colors from the theme: the frame starts unstyled, with the browser’s default text color. Leave the background unset, so Fluxer’s settings show through as they do behind FluxPlugs’ own pages.
- Clicks and scrolling work, but Fluxer’s settings dialog keeps keyboard focus to itself, so typing into fields in the page may not work. Prefer buttons, or ask for text with ui.openDialog.
Theme contract
Section titled “Theme contract”ui.theme and ui.onThemeChange expose an allowlist of CSS custom properties, each prefixed by two hyphens:
- Fluxer’s own tokens: background-primary, background-secondary, background-secondary-alt, background-tertiary, background-header-secondary, background-modifier-accent, background-modifier-hover, text-primary, text-secondary, text-primary-muted, text-tertiary, brand-primary, brand-primary-fill, status-danger, button-danger-fill, button-danger-text, font-sans, scrollbar-thumb-bg, scrollbar-thumb-bg-hover and scrollbar-track-bg.
- While your settings page is shown: settings-page-width and settings-page-gutter, the width (padding included) and side padding of the column Fluxer lays its own settings pages out in, in pixels. They change with the window size, and are empty otherwise.
- Older names kept for existing plugins, which current Fluxer doesn’t define: background-floating, text-normal, text-muted, interactive-normal, brand-500, brand-600, font-primary, input-background and input-border.
An empty value means unavailable. Provide CSS fallbacks. Parent stylesheets and DOM objects are not exposed.
Leave your page’s background unset to let the surface behind show through: the host’s view panel, or Fluxer’s settings behind a settings page. Don’t set color-scheme on the page: the host gives the frame the default scheme, and a page with a different one gets an opaque canvas instead of a transparent one. That default scheme also means the browser’s light scrollbar, so if your page scrolls, give it Fluxer’s scrollbar: html { scrollbar-color: var(--scrollbar-thumb-bg) var(--scrollbar-track-bg); }. A settings page spans the whole content area, so the scrollbar sits at its edge as on Fluxer’s pages; center your content in Fluxer’s column: main { box-sizing: border-box; max-width: var(--settings-page-width, 50rem); margin: 0 auto; padding: 20px var(--settings-page-gutter, 24px) 32px; }.
Assets and accessibility
Section titled “Assets and accessibility”Use addEventListener for handlers; remote scripts and inline event attributes are blocked. Bundled raster data URLs are allowed. Remote images must use images.load followed by images.url.
Use labels, real buttons, visible focus, responsive layouts, and reduced-motion styles. Test light and dark Fluxer themes and small windows. Remove listeners and DOM during stop, and guard asynchronous completions after shutdown.
