-
Notifications
You must be signed in to change notification settings - Fork 41
example dock decoration hooks
Where this fits. Dock customization has three layers — see the overview. This page covers the cheapest layer: decoration hooks. They compose with any rail renderer and across multiple plugins.
If you want to… Use… Add classNames, wrap tiles, animate them in Decoration hooks (this page) Replace the entire rail (ring, stack, etc.) Rail renderer
The default Dock renderer fires a small set of filters and actions while it
paints. Plugins compose decoration — animations, classNames, wrappers,
custom tooltips — through these instead of replacing the whole rail.
Status: Stable.
| Hook | Kind | Signature |
|---|---|---|
os.dock.before-render |
Action | ( ctx: DockRenderContext ) => void |
os.dock.tile-class |
Filter | ( classes: string[], ctx: DockTileContext ) => string[] |
os.dock.tile-element |
Filter | ( el: HTMLElement, ctx: DockTileContext ) => HTMLElement |
os.dock.tile-tooltip |
Filter | ( label: string, ctx: DockTileContext ) => string |
os.dock.tile-rendered |
Action | ( ctx: DockTileContext & { el: HTMLElement } ) => void |
os.dock.after-render |
Action | ( ctx: DockRenderContext ) => void |
Both context shapes carry { rail, orientation, dockId, container } so a
single subscriber can disambiguate when two rails coexist (Classic
layout's left side bar + bottom dock). dockId matches the host
element id — 'os-dock' for the bottom rail,
'os-side-dock' for the Classic side rail.
DockTileContext adds { item, isSystem }. When isSystem is true the
item is a SystemDockItem (OpenStation Preferences, plugin-owned native-window
launchers); otherwise it's a DockItem from the admin menu.
Useful for theming a specific plugin's tiles or for marking tiles you own without modifying the menu data.
wp.os.hooks.addFilter(
'os.dock.tile-class',
'my-plugin/decorate',
( classes, ctx ) => {
if ( ! ctx.isSystem && ctx.item.id === 'edit.php' ) {
return [ ...classes, 'my-plugin-glow' ];
}
return classes;
},
);CSS:
.my-plugin-glow .os-dock__item-primary {
box-shadow: 0 0 12px rgba( 255, 255, 100, 0.6 );
}Returning a different element from os.dock.tile-element
replaces the tile in the DOM. The shell still finds the original
[data-menu-slug] / [data-system-id] descendant for active-state
and badge updates, so wrap the tile, don't replace it.
wp.os.hooks.addFilter(
'os.dock.tile-element',
'my-plugin/wrap',
( el, ctx ) => {
if ( ctx.isSystem ) {
return el;
}
const wrapper = document.createElement( 'div' );
wrapper.className = 'my-plugin-tile-wrap';
wrapper.appendChild( el );
return wrapper;
},
);The filter resolves once at bind time, so it never re-fires on pointerenter. Returning an empty string suppresses the tooltip entirely.
wp.os.hooks.addFilter(
'os.dock.tile-tooltip',
'my-plugin/tooltip',
( label, ctx ) => {
if ( ! ctx.isSystem && ctx.item.badge > 0 ) {
return `${ label } — ${ ctx.item.badge } pending`;
}
return label;
},
);os.dock.tile-rendered fires once per tile after insertion,
so computed layout (offsetWidth, getBoundingClientRect) is ready.
wp.os.hooks.addAction(
'os.dock.tile-rendered',
'my-plugin/animate',
( { el, item, isSystem } ) => {
if ( isSystem || ! item.multi ) {
return;
}
el.animate(
[
{ transform: 'translateY( 8px )', opacity: 0 },
{ transform: 'translateY( 0 )', opacity: 1 },
],
{ duration: 240, easing: 'cubic-bezier( 0.2, 0.8, 0.2, 1 )' },
);
},
);os.dock.after-render fires once per pass with the full
tile element map. Use it when a decoration touches multiple tiles or
needs the post-paint geometry (e.g. measuring the rail's bounding
rect for a custom indicator).
wp.os.hooks.addAction(
'os.dock.after-render',
'my-plugin/connector',
( { tileElements, container } ) => {
// …draw a connector between two tiles, attach an
// IntersectionObserver, etc.
},
);A common decoration pattern is to drive a transform (scale,
translate) off a custom CSS property so a hover state interpolates
smoothly. By default, custom properties are typed as <string>,
which CSS can't interpolate — the transition snaps. Two paths:
-
Drive the transform inline from JS — set
el.style.transformdirectly in the relevant pointer / focus listener. Portable across every browser; the transitiontransform 180msyou set on the element interpolates the value the way CSS expects.
wp.os.hooks.addAction(
'os.dock.tile-rendered',
'my-plugin/lift',
( { el } ) => {
el.style.transition = 'transform 180ms ease';
el.addEventListener( 'pointerenter', () => {
el.style.transform = 'translateY( -2px ) scale( 1.05 )';
} );
el.addEventListener( 'pointerleave', () => {
el.style.transform = '';
} );
},
);-
Use
@property— declare your custom property with an explicitsyntaxso the browser can interpolate it:
@property --tile-scale {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
.my-plugin-tile {
transform: scale( var( --tile-scale ) );
transition: --tile-scale 180ms ease;
}
.my-plugin-tile:hover { --tile-scale: 1.05; }This is the cleaner authoring model but requires @property
support — Safari ≤15.3, Firefox ≤127, and several embedded
WebViews skip the transition silently and snap to the end value.
If your plugin must work on those, use the inline-JS path.
A custom rail renderer (see dock-rail-renderer.md)
SHOULD fire the same hooks at equivalent points so plugins that decorate
through this surface keep working when the user picks a different
renderer. The shell does not enforce this — fire idiomatic
applyFilters / doAction calls in your renderer's mount()
implementation and you're ecosystem-compatible for free.
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