-
Notifications
You must be signed in to change notification settings - Fork 41
components reference
Canonical mapping of every shipped web component: tag name → exported class → source file → one-line purpose. The runtime missing-component warner (src/ui/components/missing-import-warner.ts) points readers here.
Components are side-effect registered at import time, per bundle, into the page-global custom-element registry. The shell bundle (desktop[.min].js) registers a core subset and pre-loads shell-overlays[.min].js (the toast / confirm-dialog / context-menu / menu / select / window-chrome kit) right after first paint, so those tags upgrade anywhere once the shell is up. That is 26 of the 64 tags below. Every other component registers only when a bundle that imports its module loads — emitting a <os-foo> tag that no loaded bundle has imported renders inert HTML, and the missing-component warner logs a console.error with the exact import line to add.
Two ways to get the other 38.
-
Import the module —
import 'openstation'(the package entry re-exports the barrel, so any import registers every tag) or a single leaf module. Right for code built inside this repo, or beside it via thefile:dependency inuse-from-a-plugin.md. The class export is only needed for TypeScript types or programmatic instantiation. -
Load the kit at runtime —
await wp.os.loadComponents( [ 'os-switch' ] ). No build-time relationship with this repo required, which is what a plugin distributed as a zip has. Seewp.os.loadComponents()for the cost, andexamples/load-components.mdfor a working panel.
Neither registers a tag twice: defineComponent() no-ops on a tag the registry already has, and the runtime loader skips the fetch when the tags asked for are all present.
src/ui/components/index.ts re-exports every component class AND the OS_COMPONENT_TAGS constant — the array all the dev-time guards iterate. The constant itself is defined in src/ui/components/tags.ts (the single source of truth, kept side-effect-free); the index re-exports it. If this doc and the index disagree, the index wins. To add a new component:
- Create
src/ui/components/<name>/<name>.ts,<name>.styles.ts,<name>.test.ts. - Add the class export to
src/ui/components/index.ts. - Add the tag to
src/ui/components/tags.ts(the single source ofOS_COMPONENT_TAGS, re-exported byindex.ts). - Add a row to this table.
- Document via the
static help = { … }block on the class — surfaced in OpenStation Preferences → Components live.
OpenStation Preferences → Components (admin-only) renders this table live: every tag in OS_COMPONENT_TAGS, with its props, slots, events, parts, CSS custom properties, and a working example rendered from the static help.example template.
The tab side-effect-imports the whole component barrel so the list is the full kit rather than "whatever other bundles happen to have loaded" — the per-bundle registration model described above means an unimported component reaches no custom-element registry, and a tab that only enumerated registered tags silently under-reported itself.
The search box above the list filters on the flattened descriptor, not just the title: tag name, summary, status, static props names, and the name and description of every documented prop, slot, event, part, and CSS custom property. Terms are ANDed and order-independent, so field number and number clamp both reach <os-number-field>.
| Tag | Class | Source | Purpose |
|---|---|---|---|
<os-body> |
OsBody |
os-body/os-body.ts |
Page-body scroll container. |
<os-panel> |
OsPanel |
os-panel/os-panel.ts |
Collapsible content section with header. |
<os-section> |
OsSection |
os-section/os-section.ts |
Titled section block within a panel. |
<os-row> |
OsRow |
os-row/os-row.ts |
Horizontal flex row primitive. |
<os-stack> |
OsStack |
os-stack/os-stack.ts |
Vertical flex stack with consistent gap. |
<os-cluster> |
OsCluster |
os-cluster/os-cluster.ts |
Wrapped flex row for chips / tags / actions. |
<os-grid> |
OsGrid |
os-grid/os-grid.ts |
Auto-fit CSS grid primitive. |
<os-card> |
OsCard |
os-card/os-card.ts |
Bordered surface for entity-card UIs. |
<os-display> |
OsDisplay |
os-display/os-display.ts |
Hero / display-typography container. |
| Tag | Class | Source | Purpose |
|---|---|---|---|
<os-form> |
OsForm |
os-form/os-form.ts |
Form host with auto value-collection + validation. |
<os-field-row> |
OsFieldRow |
os-field-row/os-field-row.ts |
Label + control + hint + error. Wires the accessible pairing a light-DOM control can't get from a shadow-root <label for>: aria-describedby, aria-invalid, required, click-to-focus. |
<os-token-field> |
OsTokenField |
os-token-field/os-token-field.ts |
Text / textarea whose value contains tokens ({field:2}, {all_fields}), with a grouped catalogue that inserts at the caret and a live "reads as" preview built from each token's sample. |
<os-repeater> |
OsRepeater |
os-repeater/os-repeater.ts |
Add / remove / reorder a list of rows whose content you supply. Keyed by stable strings, so a remove or move never rebuilds the rows that didn't change. Reports intent; the consumer owns the data. |
<os-text-field> |
OsTextField |
os-text-field/os-text-field.ts |
Single-line text input. |
<os-textarea> |
OsTextarea |
os-textarea/os-textarea.ts |
Multi-line text input. |
<os-number-field> |
OsNumberField |
os-number-field/os-number-field.ts |
Numeric input with min/max/step. |
<os-color-field> |
OsColorField |
os-color-field/os-color-field.ts |
Color picker with swatches. |
<os-range-field> |
OsRangeField |
os-range-field/os-range-field.ts |
Slider with live numeric readout. |
<os-checkbox> |
OsCheckbox |
os-checkbox/os-checkbox.ts |
Standalone checkbox. |
<os-checkbox-label> |
OsCheckboxLabel |
os-checkbox-label/os-checkbox-label.ts |
Checkbox + inline label pair. |
<os-switch> |
OsSwitch |
os-switch/os-switch.ts |
On/off switch for settings that apply immediately. Tap, drag or keyboard. |
<os-select> / <os-option>
|
OsSelect, OsOption
|
os-select/os-select.ts |
Combobox trigger with a custom top-layer listbox popup. |
<os-multiselect> |
OsMultiselect |
os-multiselect/os-multiselect.ts |
Multi-select with chips. |
<os-segmented> / <os-segment>
|
OsSegmented, OsSegment
|
os-segmented/os-segmented.ts |
Segmented control (radio group as buttons). |
<os-tag-input> |
OsTagInput |
os-tag-input/os-tag-input.ts |
Free-text tag entry with autocomplete. |
<os-category-picker> |
OsCategoryPicker |
os-category-picker/os-category-picker.ts |
Category tree picker. |
<os-role-picker> |
OsRolePicker |
os-role-picker/os-role-picker.ts |
WP role select. |
<os-user-search> |
OsUserSearch |
os-user-search/os-user-search.ts |
Live user autocomplete (/desktop-mode/v1/files/users/search REST). |
The desktop shell is a real wp-admin document, so WordPress's own
forms.css is loaded and reaches every raw control you put in it:
input[ type="text" ], … , select, textarea {
background-color: #fff;
color: #1e1e1e;
border: 1px solid #949494;
}That selector weighs (0,1,1) — one type plus one attribute — which
outranks any single class of your own. A field you had already
tokenized still renders as a white core-chrome box, and on a dark
surface (a dialog, an overlay, a themed panel) the result is a bright
rectangle whose contents are painted by whichever rule did win. If
the control also pre-selects its text, the shell's ::selection —
tuned for dark surfaces — lands light ink on a pale wash over that
white, and the value is effectively unreadable.
The form components above live in shadow DOM, where forms.css cannot
follow, and they resolve the palette and the active desktop theme
instead. Use them. Raising specificity works too and is what
.os-window__body :is( input[ type ], … ) in window-chrome.css does
for admin markup we don't control — but for markup you are writing,
the component is the fix that stays fixed.
Slotting a component into a dark light-DOM surface, re-point the token
family the way <os-modal> does on its host, so the field resolves
dark-surface colours:
.my-plugin-dialog {
/* Foreground. */
--os-ui-fg: var( --os-ui-modal-text, #f0f0f1 );
--os-ui-fg-muted: var( --os-ui-modal-text-muted, #a7aaad );
--os-ui-border: var( --os-ui-modal-border, rgba( 255, 255, 255, 0.25 ) );
--os-ui-border-strong: var( --os-ui-modal-border-strong, rgba( 255, 255, 255, 0.35 ) );
/* Surface — the half that is easy to forget. */
--os-window-bg: var( --os-ui-modal-field-bg, #2c3338 );
--os-ui-surface: var( --os-ui-modal-surface, #2c3338 );
--os-ui-surface-elevated: var( --os-ui-modal-surface-elevated, #3c434a );
/* Washes, which are read against the surface they sit on. */
--os-ui-hover: var( --os-ui-modal-hover, rgba( 255, 255, 255, 0.08 ) );
--os-ui-button-bg-hover: var( --os-ui-modal-button-bg-hover, rgba( 255, 255, 255, 0.08 ) );
}Read a palette-owned --os-ui-modal-* name first in every one of
them. Declaring the literal directly would make the name unreachable
from a desktop theme — the same trap described in
desktop-themes.md.
Take the whole list, not the colours you notice. Foreground and
surface are a pair, and re-pointing only the half you can see is a bug
that hides itself: while the palette outside your dialog is dark, every
token you left behind happens to agree, and the dialog looks right. Put
a light palette outside it — Legacy,
or any theme in the admin's own colours — and the halves come apart.
--os-ui-surface is the one that bit us: it stayed #fff, so an
<os-select> in the dialog painted a white trigger and the re-pointed
--os-ui-fg wrote near-white text onto it. --os-ui-hover was a black
wash over a dark row.
If your surface is dark, you own every token that names a surface or a wash on one — not just the text.
| Tag | Class | Source | Purpose |
|---|---|---|---|
<os-button> |
OsButton |
os-button/os-button.ts |
Primary / secondary / ghost button. |
<os-window-button> |
OsWindowButton |
os-window-button/os-window-button.ts |
Title-bar icon button (minimize / maximize / close / custom). |
<os-window-button> paints an aria-hidden glyph inside a shadow
<button>, so it has no accessible name of its own — always set
aria-label on the host. The component forwards it onto that inner
button (which is the element focus lands on) and keeps it in sync when
you relabel the host, e.g. Maximize ⇄ Restore.
| Tag | Class | Source | Purpose |
|---|---|---|---|
<os-menu> / <os-menu-item>
|
OsMenu, OsMenuItem
|
os-menu/os-menu.ts |
Dropdown menu surface. |
<os-context-menu> / <os-context-menu-option>
|
OsContextMenu, OsContextMenuOption
|
os-context-menu/os-context-menu.ts |
Right-click / long-press menu. |
<os-flyout> |
OsFlyout |
os-flyout/os-flyout.ts |
Anchored popover. Supports placement strategies. |
<os-modal> |
OsModal |
os-modal/os-modal.ts |
Full-overlay modal with focus trap. |
<os-confirm-dialog> |
OsConfirmDialog, osConfirm
|
os-confirm-dialog/os-confirm-dialog.ts |
Confirm prompt — use await osConfirm({...}) (never window.confirm). |
<os-toast> / <os-toast-container>
|
OsToast, OsToastContainer
|
os-toast/os-toast.ts |
Top-right (top inline-end) toast notifications. |
<os-notice> |
OsNotice |
os-notice/os-notice.ts |
Inline informational/warning notice. |
<os-toast> hold contract. A toast reports when the user is attending to it — pointer over it, or focus anywhere inside it, including its action and close buttons in the shadow root. While that is true it carries a reflected held attribute and, on every transition, emits os-toast-hold with { held: boolean }. showToast() listens and pauses the auto-dismiss countdown for the duration; a released countdown resumes with the time it had left, floored at 1.2s so a nearly-expired toast doesn't vanish the instant the pointer leaves. held is set by the component and is not something to write by hand — a toast that should never expire on its own is persistent. Dismissing a toast that currently holds focus hands focus back to the last element outside the toast stack that had it, so clicking Undo never drops the user on <body>.
<os-confirm-dialog> keyboard contract. Opening the dialog remembers what had focus and moves focus inside; Tab and Shift+Tab cycle between the dialog's own controls and cannot reach the page behind the scrim; closing — by any route, including osConfirm() unmounting the element — hands focus back to the control that opened it. Escape always cancels. Enter is the dialog's default action only while no control inside it owns the key: with a button focused, Enter activates that button, so Enter on Cancel cancels. A danger dialog has no default action — it opens on the safe control (Cancel, or the X that dismissable adds, or the container when hideCancel leaves neither) and never on its destructive button, and Enter from the container does nothing. Reaching the destructive action is always deliberate: Tab to it, or click it.
| Tag | Class | Source | Purpose |
|---|---|---|---|
<os-icon> |
OsIcon |
os-icon/os-icon.ts |
Icon by dashicon slug or SVG content. |
<os-avatar> |
OsAvatar |
os-avatar/os-avatar.ts |
User avatar with presence dot. |
<os-badge> |
OsBadge |
os-badge/os-badge.ts |
Number badge with tone color. |
<os-ribbon> |
OsRibbon |
os-ribbon/os-ribbon.ts |
Corner ribbon for tiles. |
<os-chip> |
OsChip |
os-chip/os-chip.ts |
Tag/category chip with tone. |
<os-key> |
OsKey |
os-key/os-key.ts |
Keyboard shortcut display. |
<os-code> |
OsCode |
os-code/os-code.ts |
Inline / block monospace code with copy. |
<os-spinner> |
OsSpinner |
os-spinner/os-spinner.ts |
Loading spinner with preset variants; preset="inline" is a bare arc for text-adjacent use. |
<os-progress-bar> |
OsProgressBar |
os-progress-bar/os-progress-bar.ts |
Determinate or indeterminate progress. |
<os-save-status> |
OsSaveStatus |
os-save-status/os-save-status.ts |
Save indicator (idle / saving / saved / failed). variant="ring" is the window title bar's status ring: outline for every phase but success, which fills. |
<os-relative-time> |
OsRelativeTime |
os-relative-time/os-relative-time.ts |
Auto-updating "2 min ago". |
<os-empty-state> |
OsEmptyState |
os-empty-state/os-empty-state.ts |
Empty-list / no-results placeholder. |
<os-rating-summary> |
OsRatingSummary |
os-rating-summary/os-rating-summary.ts |
Star average + per-star bucket bars. |
| Tag | Class | Source | Purpose |
|---|---|---|---|
<os-table> |
OsTable |
os-table/os-table.ts |
Sortable, filterable data table with sub-tables. |
<os-log> |
OsLog |
os-log/os-log.ts |
Virtualized streaming log container. |
<os-tile> |
OsTile |
os-tile/os-tile.ts |
Desktop-style icon tile (used by the desktop file layer, folder windows, and WP Explorer). selectable switches it from listitem to option so it can carry aria-selected — the selection controller sets it. |
| Tag | Class | Source | Purpose |
|---|---|---|---|
<os-tabs> / <os-tab> / <os-tabpanel>
|
OsTabs, OsTab, OsTabPanel
|
os-tabs/os-tabs.ts |
Tab strip with associated panels, for a tab group inside content. A window's own top-level tabs belong in the window chrome instead — see Window.setTabs() in javascript-reference.md. |
<os-tab-chip> |
OsTabChip |
os-tab-chip/os-tab-chip.ts |
Single chip tab (e.g. window tabs). |
<os-steps> / <os-step>
|
OsSteps, OsStep
|
os-steps/os-steps.ts |
Wizard step indicator. |
<os-crumb-chain> |
OsCrumbChain |
os-crumb-chain/os-crumb-chain.ts |
Breadcrumb trail with chevron separators. |
| Tag | Class | Source | Purpose |
|---|---|---|---|
<os-swatch> |
OsSwatch |
os-swatch/os-swatch.ts |
Single color swatch button. |
<os-swatch-grid> |
OsSwatchGrid |
os-swatch-grid/os-swatch-grid.ts |
Grid of color swatches with selection. |
The kit wears the OpenStation brand, and the brand ships five mesh gradients with one instruction attached: "meshes reserved for hero surfaces." src/ui/holo.ts is how a control gets to be one without every component reinventing what holographic means.
It is a moment, not a skin. Form controls (checkboxes, radios, switches, sliders, the segmented thumb) wear the flat accent when they are on; selection and state across the kit resolve through --os-ui-accent, which follows the accent the user picked in OpenStation Preferences; the mesh appears only where a single surface speaks for the brand, such as <os-button variant="holo">. A panel where every surface is iridescent has no identity moments left to spend.
Three treatments, in ascending loudness:
| Fragment | What it does | Who gets it |
|---|---|---|
holoEdge |
An iridescent hairline, invisible at rest, lit on hover and focus. |
<os-button> (all variants but link/danger), and the selected state of <os-chip>, <os-card>, <os-swatch>. |
holoSheen |
A ~10%-alpha film of the mesh's hues over the existing surface, faded in under the pointer. |
<os-button>, <os-key>, unselected <os-segment>. |
holoFill |
The mesh itself, at full strength, with Void ink on top. | The on state of <os-switch>, checked <os-checkbox> / <os-checkbox-label> / <os-menu-item>, the selected <os-segment>, the elapsed track of <os-range-field>, the fill of <os-progress-bar>, the <os-step> chip, and <os-button variant="holo">. |
…and four motions, which are what make the surfaces read as foil rather than as paint:
| Fragment | What it does | Who gets it |
|---|---|---|
holoGlint |
A specular band crosses the surface once on hover. The single most "holographic" thing in the kit. |
<os-button>, <os-key>, interactive <os-card>. |
holoRing |
A ring expands out of the control and fades on :active, so a press reads as received before its result paints. |
<os-button>, <os-key>. |
holoShimmer |
The mesh travelling, for waits of unknown length. | Indeterminate <os-progress-bar>. |
holoEnter |
Scale-and-fade arrival on the spring. |
<os-menu>, <os-context-menu>, <os-modal>, <os-confirm-dialog>. |
Plus the motions that belong to one component and stayed there: the <os-segmented> thumb that slides between segments, the <os-tabs> underline that grows from the centre, the <os-switch> knob's spring-and-squash, the <os-checkbox> tick landing with an overshoot, <os-toast> arriving from above and leaving sideways, and the <os-avatar> presence ring — which pulses only on online, so "who is here" survives being read by someone who cannot separate the three dot colours.
The pseudo-element budget. An element has two, and this module wants four effects. holoSheen takes ::before and holoEdge takes ::after — that is the whole budget for a control wearing both, as <os-button> does. So holoGlint and holoRing are element-based: the component stamps a <span class="os-holo-glint"> / <span class="os-holo-ring"> and the fragment styles it. Both are driven from the parent's state via the child combinator (:active > .os-holo-ring), which is load-bearing: :active matches an activated element and every ancestor of it, so a descendant selector would fire every ring on the page.
Two shared fragments carry the states that are not decorative:
-
holoField— one hover, one focus ring, one transition duration and one placeholder colour for every text-like control (<os-text-field>,<os-textarea>,<os-number-field>,<os-select>, and any component that renders a bareinput/select/textareain its shadow root). Its selectors wrap their type exclusions in:where()so a component's ownaria-invalidring still outranks it — an invalid field focuses in red, not in Pulse. -
holoCheck— the checkbox and radio paint, shared by<os-checkbox>,<os-checkbox-label>and<os-table>'s selection column. It replacedaccent-color, which takes a colour where the checked state here is a gradient.
Declared in assets/css/variables.css, on body.os-active (never :root — the file also loads inside every iframe window). Every component reads them through a private --_holo-* alias, so a desktop theme can re-point any of them and the whole kit changes together.
| Token | Meaning |
|---|---|
--os-mesh-holo / -pulse / -auro / -star / -mio
|
The brand's five meshes, transcribed stop-for-stop from the SVGs into CSS gradient stacks. |
--os-ui-holo-fill |
What an "on" surface paints. Holomesh by default. |
--os-ui-holo-ink |
Glyphs and text on that fill. Void — every mesh in the brand is a light surface. |
--os-ui-holo-sheen |
The hover film. |
--os-ui-holo-edge / --os-ui-holo-edge-quiet
|
The iridescent hairline, lit and at rest. |
--os-ui-holo-glow / --os-ui-holo-glow-strong
|
The Pulse bloom around a lit surface. |
--os-ui-holo-track |
The unlit half — switch tracks, empty progress. |
--os-ui-tab-edge |
The selected row's leading edge in a vertical <os-tabs>. The flat accent, so the row says "this one" in the same colour as every control beside it. |
--os-ui-tab-wash / --os-ui-tab-bloom
|
That row's surface wash, and the bloom the edge throws back across it. Both ambient, so both resolve through --os-ui-accent-dim. |
--os-ui-accent-dim |
Pulse one step back (same hue, S and L pulled down together). The single knob for how loud the station is — every ambient use of the accent resolves through it. |
--os-ui-focus-ring |
The target ring: buttons, switches, checkboxes, swatches. Built to survive landing on a bright mesh. |
--os-ui-focus-ring-field |
The field ring: quieter, tightens the input's own border. A form of twelve inputs should not look alarmed. |
--os-ui-motion-fast / --os-ui-holo-transition / --os-ui-motion-slow / --os-ui-motion-ambient
|
The duration scale: a state flip, the default tilt, something crossing a distance, an ambient loop. |
--os-ui-ease-spring / --os-ui-ease-out / --os-ui-ease-loop
|
The three curves. spring overshoots ~9% and is wrong for anything that changes size. |
--os-ui-accent is Pulse #f252fc and stays there — it is what the brand guidelines name, and brand-palette.test.ts pins it. Pulse is also not a contrast problem: it carries 6.2:1 against Obsidian.
What makes a panel read as loud is the ambient use — a bloom behind a focused control, an 18% wash under a selected row, a fill wider than a chip. Those all resolve through --os-ui-accent-dim, so:
body.os-active { --os-ui-accent-dim: #b02ab8; } /* quieter still */is the whole edit. The focus ring deliberately does not follow it — only the bloom behind the ring does. A focus indicator is the last place to trade legibility for calm.
Every fragment honours prefers-reduced-motion by stopping the tilt — never by removing the fill. A control that lost its mesh under reduced motion would lose its state, not just its animation.
import { css } from '../../core';
import { holoTokens, holoEdge, holoFill } from '../../holo';
export const styles = css`
${ holoTokens }
${ holoEdge }
${ holoFill }
button:focus-visible { box-shadow: var( --_holo-focus ); }
`;holoTokens declares the aliases the others read — include it once per component. Import holo instead for the whole vocabulary.
Two rules the guards enforce (tests/vitest/holo-layer.test.ts, tests/vitest/component-token-reachability.test.ts):
-
Never declare a
--os-ui-*name on a bare:host. A property declared on the host beats anything it would inherit, so it kills the palette's and every theme's declaration of that name. Read the public token into a private--_aliasinstead. -
Comments inside a
css`` template cannot contain backticks. The template is a JS template literal; a backtick in a CSS comment terminates it and the file stops parsing.
import { OsLog, type OsLogRowRenderer } from 'openstation';Not every class in the tables above is importable from 'openstation'. The package exports map exposes only the entry point (src/public-api.ts), which re-exports the Stable kit: OsAvatar, OsBadge, OsButton, OsCheckboxLabel, OsCluster, OsCode, OsColorField, OsDisplay, OsEmptyState, OsGrid, OsIcon, OsKey, OsLog, OsMenu, OsMenuItem, OsPanel, OsRangeField, OsSection, OsSegment, OsSegmented, OsStack, OsStep, OsSteps, OsSwatch, OsSwatchGrid, OsTab, OsTabChip, OsTabs, OsTextarea, OsToast, OsToastContainer, OsWindowButton. If src/public-api.ts and this list disagree, the source wins.
The remaining classes are internal-only for now — subpath / source-path imports are blocked by the exports map — though their tags still work wherever a loaded bundle has registered them. The class import is for type-checking, subclassing, or programmatic instantiation; importing anything from 'openstation' also registers every tag as a side effect. See use-from-a-plugin.md for the local-install workflow.
Every class has a static help = { … } block with full props / slots / events / examples / status. The OpenStation Preferences → Components tab iterates OS_COMPONENT_TAGS and renders these descriptors live; that's the authoritative per-component reference. The table above is a directory; the static help block is the manual.
This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config