# desktop-mode/trunk/assets/css/app-runtime.css

OpenStation: Desktop Windows, Dock &amp; Virtual Desktops for WP Admin, version trunk. 359 lines.

- Page: https://pluginprobe.com/plugins/desktop-mode/trunk/code/assets/css/app-runtime.css
- Raw: https://pluginprobe.com/plugins/desktop-mode/trunk/raw/assets/css/app-runtime.css
- Modified: 2026-09-04T15:30:56+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/desktop-mode/trunk/code/assets/css/app-runtime.css#L10-L20`.

```css
/**
 * OpenStation — App Framework runtime styles.
 *
 * The root every `.os.php` window mounts into. Fills the window
 * body, declares itself a container so an app's stylesheet can use
 * `@container` queries against the window's width, and centres the
 * first-paint spinner until the `mount` render lands.
 */

.os-app {
	display: flex;
	flex-direction: column;
	inline-size: 100%;
	block-size: 100%;
	min-block-size: 0;
	overflow: hidden;
	background: var( --os-ui-surface, #f0f0f1 );
	color: var( --os-ui-fg, #1d2327 );
	font-family: var(
		--os-ui-font,
		-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
		'Helvetica Neue', Arial, sans-serif
	);
	container-type: inline-size;
}

.os-app__loading {
	display: flex;
	flex: 1;
	align-items: center;
	justify-content: center;
}

/* A dispatch is in flight: dim nothing, just signal it to AT and
   let an app style `[aria-busy]` if it wants a visible cue. */
.os-app[aria-busy='true'] {
	cursor: progress;
}

/* The flex-row gap filler every toolbar ends up needing. */
.os-app__spacer {
	flex: 1;
}

/* The drawn selection box `createMarquee()` appends to the body —
   the same canonical selection tokens the shell's tiles wear. */
.os-app__marquee {
	position: fixed;
	z-index: 100000;
	border: 1px solid
		var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) );
	border-radius: 2px;
	background: var(
		--os-selection-marquee-bg,
		var( --os-ui-accent-soft, rgba( 34, 113, 177, 0.14 ) )
	);
	pointer-events: none;
}

/* ---------- The ⋯ menu section --------------------------------------- */

/* A checkbox section `mountMenuCheckboxes()` appends to the window's
   title-bar ⋯ menu ("Show columns"). The label is a small-caps
   separator; the toggles are real <os-menu-item role="menuitemcheckbox">
   nodes, so they pick up the menu's own styling. */
.os-app__menu-section {
	margin-top: 4px;
	padding: 6px 12px 2px;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var( --os-ui-fg-muted, #50575e );
	border-top: 1px solid var( --os-ui-border, #dcdcde );
	pointer-events: none;
}

/* ---------- List windows --------------------------------------------- */

/* The layout every list window shares (Posts, Pages, Users, and any
   list a plugin ships): a toolbar across the top, a flex-1 body that
   hosts an <os-table>, a footer pager. The table is told to size to
   the body via `--os-ui-table-max-height: 100%` so its sticky header
   engages without an outer scrollbar. Cell-content styles are NOT
   here — <os-table> renders into its own shadow DOM, which document
   stylesheets never reach; cells carry inline styles. */
.os-app-list {
	display: flex;
	flex-direction: column;
	/* A query container, so the narrow rules below track the WINDOW's
	   width — a phone, or a desktop window pulled in. */
	container-type: inline-size;
	height: 100%;
	width: 100%;
	min-height: 0;
	background: var( --os-ui-surface, #fff );
	color: var( --os-ui-fg, #1d2327 );
}

/* Tabs strip — auto-height; only the active panel below grows. */
.os-app-list__tabs {
	flex: 0 0 auto;
}

/* Sibling panels under one flex column. The hidden ones are UA
   display:none via the `hidden` attribute; the visible one stretches
   to fill the remaining height. */
.os-app-list__panel {
	flex: 1 1 auto;
	min-height: 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

.os-app-list__panel[hidden] {
	display: none;
}

.os-app-list__toolbar {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 12px;
	padding: 10px 14px;
	border-bottom: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
	background: var( --os-ui-surface-elevated, #f6f7f7 );
}

.os-app-list__toolbar-left,
.os-app-list__toolbar-right,
.os-app-list__toolbar-trailing {
	display: flex;
	align-items: center;
	gap: 8px;
}

/* Honour the `hidden` attribute over the explicit `display: flex`. */
.os-app-list__toolbar-right[hidden] {
	display: none;
}

.os-app-list__toolbar-trailing {
	margin-inline-start: auto;
}

.os-app-list__count {
	font-size: 12px;
	font-weight: 600;
	color: var( --os-ui-fg-muted, #50575e );
	font-variant-numeric: tabular-nums;
	padding-inline-end: 4px;
}

.os-app-list__body {
	flex: 1 1 auto;
	/* `min-width: 0` is the load-bearing piece — without it the flex
	   item's intrinsic width (the table's column sum) becomes its hard
	   minimum, pushes the table past the window's edge and turns the
	   WHOLE WINDOW into a horizontal scroller. With it the item shrinks
	   and the table's own scroll container takes the overflow. */
	min-width: 0;
	min-height: 0;
	display: flex;
	padding: 8px 12px;
}

.os-app-list__body os-table {
	flex: 1 1 auto;
	min-width: 0;
	--os-ui-table-max-height: 100%;
}

.os-app-list__empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 12px;
	padding: 48px 24px;
	color: var( --os-ui-fg-muted, #50575e );
	text-align: center;
}

.os-app-list__empty .dashicons {
	font-size: 40px;
	width: 40px;
	height: 40px;
	color: var( --os-ui-fg-faint, #a7aaad );
}

.os-app-list__empty p {
	margin: 0;
}

.os-app-list__empty-hint {
	font-size: 13px;
	color: var( --os-ui-fg-muted, #50575e );
}

.os-app-list__pager {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 10px 14px;
	border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
	background: var( --os-ui-surface-elevated, #f6f7f7 );
	font-size: 13px;
}

.os-app-list__pager-meta {
	color: var( --os-ui-fg-muted, #50575e );
	font-variant-numeric: tabular-nums;
}

.os-app-list__pager-nav {
	display: flex;
	align-items: center;
	gap: 8px;
}

.os-app-list__pager-perpage {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	color: var( --os-ui-fg-muted, #50575e );
	font-size: 12px;
	margin-inline-start: 8px;
}

/* Narrow windows — a phone, or a desktop window pulled in. The toolbar
   gives the status control and the search a row each instead of
   squeezing them beside the buttons, the insets shrink to what a 360px
   screen can spare, and the pager wraps rather than clipping its
   per-page control. */
@container ( max-width: 600px ) {
	.os-app-list__toolbar {
		gap: 8px;
		padding: 8px 10px;
	}
	.os-app-list__toolbar-left {
		flex: 1 1 100%;
		flex-wrap: wrap;
	}
	.os-app-list__toolbar-left > .os-app-list__status {
		flex: 1 1 100%;
		min-width: 0;
	}
	.os-app-list__toolbar-left > .os-app-list__search {
		flex: 1 1 160px;
		min-width: 0;
	}
	.os-app-list__toolbar-right {
		flex: 1 1 100%;
		flex-wrap: wrap;
	}
	.os-app-list__body {
		padding: 6px 8px 8px;
	}
	.os-app-list__pager {
		flex-wrap: wrap;
		gap: 8px;
		padding: 8px 10px;
	}
}

/* The same declarations, keyed on the shell's phone mode: the phone
   experience at any width (a wide browser previewing it, a phone
   turned sideways), which no size query can express. A container
   query and a stamp selector cannot share one rule list, so the
   block is repeated on purpose — keep the two in step. */
html[data-os-mode="mobile"] .os-app-list__toolbar {
	gap: 8px;
	padding: 8px 10px;
}
html[data-os-mode="mobile"] .os-app-list__toolbar-left {
	flex: 1 1 100%;
	flex-wrap: wrap;
}
html[data-os-mode="mobile"] .os-app-list__toolbar-left > .os-app-list__status {
	flex: 1 1 100%;
	min-width: 0;
}
html[data-os-mode="mobile"] .os-app-list__toolbar-left > .os-app-list__search {
	flex: 1 1 160px;
	min-width: 0;
}
html[data-os-mode="mobile"] .os-app-list__toolbar-right {
	flex: 1 1 100%;
	flex-wrap: wrap;
}
/* The phone's list is a card per row running edge to edge. */
html[data-os-mode="mobile"] .os-app-list__body {
	padding: 0;
}
html[data-os-mode="mobile"] .os-app-list__pager {
	flex-wrap: wrap;
	gap: 8px;
	padding: 8px 10px;
}

/* The phone's list is a card per row (<os-table stacked>, set through
   `stack-on-phone.ts`) running edge to edge, and the selection's
   actions are a bar along the bottom of the panel — where the thumb
   is, and still there once the toolbar has scrolled away. The bar is
   a flex sibling of the body, so the cards end where it begins; it
   clears the home indicator on an installed app. */
.os-app-list__toolbar-right.os-app-list__bulk--footer {
	flex: 0 0 auto;
	flex-wrap: wrap;
	gap: 8px;
	padding: 10px 12px calc( 10px + env( safe-area-inset-bottom, 0px ) );
	border-block-start: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
	background: var( --os-ui-surface-elevated, #f6f7f7 );
	box-shadow: var( --os-ui-shadow-sheet, 0 -6px 18px rgba( 0, 0, 0, 0.06 ) );
}

.os-app-list__toolbar-right.os-app-list__bulk--footer[hidden] {
	display: none;
}

.os-app-list__toolbar-right.os-app-list__bulk--footer .os-app-list__count {
	flex: 1 1 100%;
}

.os-app-list__toolbar-right.os-app-list__bulk--footer .os-app-list__bulk-actions {
	display: flex;
	flex: 1 1 100%;
	flex-wrap: wrap;
	gap: 8px;
}

.os-app-list__toolbar-right.os-app-list__bulk--footer .os-app-list__bulk-actions > * {
	flex: 1 1 auto;
}

/* ---------- Tone contract -------------------------------------------- */

/* Any element inside an app root may carry `data-tone` and read the
   matching status colour back through `--os-app-tone` (a swatch fill, a
   row's accent border). Scoped to `.os-app` so the mapping never
   retints shell chrome or admin pages outside an app window. */
.os-app [data-tone='danger'] {
	--os-app-tone: var( --os-ui-danger, #d63638 );
}

.os-app [data-tone='warning'] {
	--os-app-tone: var( --os-ui-warning, #dba617 );
}

.os-app [data-tone='neutral'] {
	--os-app-tone: var( --os-ui-fg-muted, #646970 );
}

.os-app [data-tone='info'] {
	--os-app-tone: var( --os-ui-info-fg, #72aee6 );
}

```
