Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / UI

The app shell (rail, work area, tabs)

osy docs sample admin-shell — the frame an admin-shaped app is built inside, as source you own

An application shell — a canvas, a floating card, a collapsing side rail with a scrolling nav, a work area, an app bar and open-document tabs — ships as a SAMPLE you copy, not as kit controls you reference. A shell is one navigation model rather than a general one, and it is where an app's identity lives, so the source is yours from the first day.

previewuikitlayoutnavigation

Summary#

Most applications with more than a handful of screens end up with the same frame: a side rail you navigate from, a work area the current screen renders into, and something pinned top and bottom. Building it is a day's work, and it is the same day in every app.

So the platform ships one — as an app you copy, not as controls you reference:

$ osy docs sample admin-shell --into ./my-app
$ cd my-app && osy launch

You get thirteen components in model/shell.osy that are yours to edit, a [Layout] that assembles them, and three routed pages to prove the frame works. Everything they are built fromButton, Avatar, IconButton, Spacer, the type roles, and the theme it all resolves against — stays Osyrin.Ui (the UI kit) and keeps improving without you.

Signature#

$ osy docs sample admin-shell --into ./my-app     # the whole shell, as your source
AppFrame { AppCard { SideRail { … } WorkArea { AppBar { … } Outlet(retain: 4); } } }

Description#

Why this is a sample and not a kit control#

This used to be kit surface, and moving it out was a decision worth stating.

A shell is ONE navigation model, not a general one. These parts encode a collapsible left rail. Measured across the apps in this repository: an expense app is a top bar and used none of them; a file manager is a two-pane workspace and used none of them; the admin console is the rail — which is where they were extracted from. A library that serves the app it came from is a sample with extra ceremony.

A shell is where an app's identity lives. Shipping one as a library means every app that opts into the kit looks like the same product until it deliberately opts out — and opting out of thirteen components is not a small act.

And it claimed generic names. NavItem and Brand are words every app wants. While the kit held rail-specific controls under them, an app's own top-bar pill collided with the library and had to justify itself in a comment.

The general rule this follows: genuinely reusable composables are the kit; the thing that makes your app yours is yours. A shell composes reusable controls — it is just not one itself.

What the sample gives you — the parts#

PartWhat it is
AppFrameThe outer canvas — a padded backdrop on a tablet and up, full-bleed on a phone.
AppCardThe floating card the app lives in. It clips its children, so a rail's edge and a table's scrollbar both stay inside the rounded corners.
SideRailThe navigation column. Collapses to an icon strip.
RailHeader / RailNav / RailFooterPinned top, scrolling middle, pinned bottom. Only the middle grows.
RailSectionA labelled group of nav items; the label hides when collapsed.
NavItemOne entry. current is the caller's to decide — a nav item has no business reading the URL.
WorkAreaThe column the routed page renders into.
AppBarA bar inside the work area — a hamburger row, a breadcrumb strip, a page toolbar.
BrandA mark in the slot, the product name beside it.
WorkspaceTabs / WorkspaceTabOpen-document tabs, each with its own lifetime — a different model from Osyrin.Ui (the UI kit)'s Tabs, which switch a view within one page. An app wants one or the other, never both.

There is no AppShell component — you assemble it#

Note what is not in that table: an AppShell. The moment a shell knows your routes, your navigation guard or your nav list it stops being copyable, so the sample keeps the two apart — structure in model/shell.osy, policy in the [Layout] below. That split is what lets you keep the frame and throw away every decision in it.

Three details that are load-bearing#

WorkArea sets MinW = 0

Without it, a wide table inside the work column shoves the entire shell sideways instead of scrolling within its own column — the single most common admin-layout bug, and invisible until real data arrives.

Why tabs keep their state — Outlet(retain: …)

retain is what makes tabs feel like tabs and makes going "back" cheap: a retained page keeps its state, so switching away and returning does not throw away a half-filled form or re-run its reads. See routes and pages.

A WorkspaceTab's close button is a separate hit target

Closing never also selects. A close that selects first is the classic way to lose someone's work.

Examples#

The sample's own [Layout] is above — it IS the example, pulled from the app the docs build compiles. Scaffold it and change one thing at a time:

$ osy docs sample admin-shell --into ./my-app
$ cd my-app && osy launch

See also#

Related

Osyrin.Ui (the UI kit)

The bundled UI kit — ready-made styled controls like `Button`, the shared design-system vocabularies (`Tone`, `Size`)…

routes and pages

How a component becomes a page: it declares a route with `[Page("/catalog/{slug}")]`, and navigating to a matching path…

Slot (child content)

A `Slot` marks where a component renders the content block its caller wrapped around it. Writing `Card { Text("hi"); }`…

layout primitives

The built-in layout primitives and how they arrange children. `Stack` stacks children in a column, `Row` lays them in a…