-
Notifications
You must be signed in to change notification settings - Fork 41
example window controls
The title-bar control cluster (close / minimize / maximize / focus) is rendered from a registry plugins can extend, reorder, hide, or replace per-window. Built-in controls live in the same registry as plugin controls, addressed by the stable ids core/minimize, core/maximize, core/focus-tab, core/close. (Detach and reload moved into the title-bar three-dots menu and are no longer registry controls.)
This is Layer 2 of the four-layer window-chrome customization framework. See Window themes for Layer 1.
Move the close button to the leftmost position on a specific native window:
wp.os.registerWindow( {
id: 'my-plugin/dashboard',
title: 'Dashboard',
icon: 'dashicons-dashboard',
width: 640, height: 480,
minWidth: 320, minHeight: 200,
appearance: {
controls: {
order: [ 'core/close', 'core/minimize', 'core/maximize' ],
},
},
render: ( body ) => { body.textContent = 'Hello'; },
} );Controls listed in order render in that order; controls not listed keep their registry order and append after.
wp.os.applyWindowControls( 'edit-post', {
hide: [ 'core/focus-tab' ],
} );Other windows retain the full set. Pass null to clear the override.
A control declared inline never enters the global registry — it lives only on this window:
wp.os.applyWindowControls( 'edit-post', {
custom: [
{
id: 'my-plugin/star',
label: 'Star this draft',
icon: 'dashicons-star-filled',
placement: 'controls', // alongside close/min/max
order: 5, // before core/minimize (order 10)
onClick: ( ev ) => {
console.log( 'starred', ev );
},
},
],
} );placement: 'controls' puts the button inside the cluster. 'left' and 'right' are accepted but currently route through the legacy title-bar-button registry — see Connect to a window for the established pattern.
When the same control should appear in many windows, register it via wp.os.registerWindowControl() with a match predicate:
plugin.php
add_action( 'admin_enqueue_scripts', function () {
wp_register_script(
'my-plugin-controls',
plugins_url( 'controls.js', __FILE__ ),
array( 'openstation' ),
'1.0.0', true
);
wp_enqueue_script( 'my-plugin-controls' );
} );
openstation_register_window_control_script( 'my-plugin-controls' );controls.js
wp.os.whenReady( () => {
wp.os.registerWindowControl( {
id: 'my-plugin/info',
label: 'Info',
icon: 'dashicons-info',
placement: 'controls',
order: 5,
match: ( win ) => win.config.url?.includes( 'post.php' ) ?? false,
owner: 'my-plugin-controls', // for live unregister on deactivation
onClick: ( win ) => {
console.log( 'info clicked on', win.id );
},
} );
} );The owner field is the WP script handle. Deactivation drops every control with this owner without F5.
wp.os.unregisterWindowControl( 'core/focus-tab' );Re-register at any time to bring it back; the registry is a Map and entries replace by id.
wp.os.applyWindowControls( 'my-plugin/dashboard', {
placement: 'left',
} );Sets the os-window__controls--left class on the cluster — your CSS theme can react to that for the actual layout flip.
When you don't want to register or unregister, use the os.window.chrome.controls filter to mutate the list at paint time:
wp.hooks.addFilter(
'os.window.chrome.controls',
'my-plugin/never-close-the-shop',
( controls, ctx ) => {
if ( ctx.placement !== 'controls' ) return controls;
// Hide close on the woocommerce shop window.
if ( ctx.config.url?.includes( 'admin.php?page=wc-admin' ) ) {
return controls.filter( ( c ) => c.id !== 'core/close' );
}
return controls;
}
);| Hook | Type | Signature | Purpose |
|---|---|---|---|
openstation_window_control_script_registered |
action | ( string $handle ) |
Fires after openstation_register_window_control_script() succeeds. |
openstation_window_control_registered |
action | ( string $id, array $entry ) |
Fires after openstation_register_window_control() stores metadata. |
| Hook | Type | Signature | Purpose |
|---|---|---|---|
os.window.chrome.controls |
filter | ( controls, { windowId, config, placement } ) => controls |
Mutate the resolved per-placement control list. Stable. |
os.window.chrome.applied |
action | ( { windowId, layer } ) |
Fires after a paint completes. layer is 'controls' for this layer. Stable. |
| Function | Purpose |
|---|---|
wp.os.registerWindowControl( def ) |
Register a global control. Throws on validation failure. |
wp.os.unregisterWindowControl( id ) |
Drop by id. No-op if not registered. |
wp.os.listWindowControls() |
Snapshot for tooling / inspectors. |
wp.os.applyWindowControls( windowId, override ) |
Per-window mutation at runtime. Pass null to clear. |
WindowConfig.appearance.controls |
Per-window declaration at registration time. |
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