-
Notifications
You must be signed in to change notification settings - Fork 42
example app layouts
os-app-frame, os-split, and os-grid, including automatic column fitting
and child column/row spans, are Stable.
Use these in PHP views or client html templates. Load the components first:
await wp.os.loadComponents( [ 'os-app-frame', 'os-split', 'os-grid', 'os-panel', 'os-stack', 'os-cluster', 'os-button' ] ),
or import their leaf modules when building in this repository. Components appear
in Preferences → Components with live examples.
<os-grid min-item-width="240" gap="12">
<os-panel col-span="2">Main chart</os-panel>
<os-panel row-span="2">Activity</os-panel>
<os-panel>Orders</os-panel>
<os-panel>Visitors</os-panel>
</os-grid>min-item-width is a positive pixel number: the grid fits as many equal columns
as its own content width allows, including the computed column gap. It overrides
columns; removing it restores the fixed-column behavior. Below the minimum,
a single column fits the available width without horizontal overflow. This is
independent of viewport width and works inside nested panes.
Direct children of any element type can declare col-span="1" through "12"
and row-span="1" through "12". Column spans clamp to the available columns;
in automatic one-column mode, row spans and the explicit rows count reset to
content sizing. Invalid spans behave as ordinary one-cell items. Spans never
cross between separate grids. Placement follows DOM order; no dense packing or
visual reordering changes the reading order. Existing os-row child col
attributes retain their twelve-track meaning.
The existing columns, rows, gap, column-gap, row-gap attributes and
--os-ui-grid-* tokens remain available. Gaps are nonnegative pixel integers;
row/column counts are positive integers. Removed or invalid attribute overrides
restore the prior inline token, or fall back to stylesheet defaults. Use columns or min-item-width when
using spans so the grid knows the track count.
<os-app-frame style="height: 480px">
<h2 slot="header">Preferences</h2>
<os-stack gap="16">
<os-panel>Account fields</os-panel>
<os-panel>Notification fields</os-panel>
</os-stack>
<os-cluster slot="footer" justify="end">
<os-button variant="primary">Save</os-button>
</os-cluster>
</os-app-frame>The frame fills a parent with a definite height; standalone examples set one
explicitly. header, toolbar, and footer slots take their content height.
The default slot gets the remaining height and scrolls. The frame owns no
padding, colors, landmarks, or toolbar role: use panels for insets and provide
appropriate semantics on your slotted content. ::part(content) exposes the
body region for app styling.
contained disables frame scrolling and stretches default-slot children into
the available height. Use it with a split pane, table, or other component that
owns its scrolling. Avoid nesting multiple scrolling wrappers unnecessarily.
<os-app-frame contained style="height: 480px">
<os-cluster slot="toolbar"><os-button>New item</os-button></os-cluster>
<os-split resizable label="Resize item list" position="35"
min-start="180" min-end="260" collapse-at="600" narrow="start">
<os-panel slot="start">Item list</os-panel>
<os-panel slot="end">Selected item</os-panel>
</os-split>
<span slot="footer">Ready</span>
</os-app-frame>Each pane fills its region and scrolls independently. position is the start
pane's percentage of space excluding the 8px divider, default 35. min-start
and min-end are pixel minima, default 160 each. When both cannot fit, they
scale proportionally. Without resizable, there is no interactive divider.
::part(start), ::part(end), and ::part(divider) expose the regions;
the divider keeps an 8px pointer target around a hairline and rounded grip.
The seam reads --os-ui-border, the grip reads --os-ui-fg-faint,
and hover, dragging and keyboard focus use --os-ui-accent. Reduced motion
disables the color transition.
At collapse-at pixels or narrower (default 600), horizontal splits use narrow:
stack (default) shows both panes vertically, start or end shows only that
pane. Set collapse-at="0" to disable automatic collapse, or compact to force
it. Narrow mode disables resizing and preserves the requested wide position.
The app decides when to set narrow="end" after selection and must provide its
own Back control to set narrow="start". Hidden panes remain mounted, preserving
drafts and component state. Comments uses this pattern, including compact
when the shell explicitly selects phone mode.
Provide a translated label for the focusable separator. Arrow keys move it
2 percentage points; Shift moves 10; Home/End go to the pane limits. Horizontal
arrows follow physical direction in RTL. Escape or pointercancel rolls back
a drag. Losing pointer capture without a preceding cancellation commits the
last visible position, even if pointerup never reaches the separator.
Layout reflow during a drag keeps it active at its current position;
crossing into narrow mode cancels it. Pointer capture and a temporary shield
keep dragging reliable over iframes. The separator exposes orientation, current value, limits and its
controlled pane to assistive technology. Layout containers add no tab stops
apart from an enabled separator.
Committed user resizing emits a bubbling, composed os-split-change with
{ position: number }. Attribute changes and container resizing emit nothing.
No storage is written; apps own persistence and may listen or use
os-action="save_layout" (the runtime passes $args['position']). Do not use
os-bind for this event: its payload has position, not value.
<os-split direction="vertical" resizable label="Resize preview"
min-start="100" min-end="100" style="height: 480px">
<os-panel slot="start">Editor</os-panel>
<os-panel slot="end">Preview</os-panel>
</os-split>Vertical splits use Up/Down keys and height-based minima. They do not
collapse automatically based on width. Nested splits are supported; each one
measures its own available space. compact can explicitly force narrow mode.
For basic rows, columns and twelve-track forms, see layout primitives.
For responsive fields with labels, hints and errors, use os-form and
os-field-row from the component kit.
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
- The App Framework — a window in one PHP file
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Data model — where OpenStation keeps its data
- 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
- Window-scoped MIO
- Mio
- Mobile — the phone layer
- Multisite
- Native Windows & Framework Interop
- OpenStation Network
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
- Workspaces
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration — Code Blue becomes an App Framework app
- Migration — AI comment scoring leaves core
- Migration: window, wallpaper and widget bundles load on demand
- Migration — Posts, Pages, Users, User Edit, Plugins and Comments become App Framework apps
- Migration — the navigation model
- Migration — OpenStation Preferences becomes an App Framework app
- Performance settings move to Extended options
- Presence storage migration
- Migration — the Recycle Bin becomes an App Framework app
- Migration — the shell boots from its own screen
- Migration — Station Home becomes an App Framework app
- Migration: a native window's tabs move to the window chrome
- Migration — WP Explorer becomes the my-wordpress app
- Migration — WordPress package globals are no longer ambient
More
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- App layout recipes
- 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
- Repairable form edits with MIO
- Register a window companion
- Pin your app to the phone tab bar, and react to the mode
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- WP Explorer — add a column to the list view
- 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
- Ship a window as an .os.php app
- 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
- Revisions in their own window — extend or redirect "View revisions"
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Observe stored-file cleanup failures
- 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
- Place something where the user can reach it — wp.os.workArea
- Ship a workspace template