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

Reference / UI

The UI (components, and the five layers under them)

Every screen in an Osy# app is a `component` — the one archetype. A page is a component with a route on it, a layout is a component, a reusable widget is a component; `[Page]`, `[Composable]` and the rest are attributes on that one thing, not separate kinds. State is its fields, `live var` is the field that keeps up with the database, and `render { }` is the tree. This is the largest area in the language, so the page below it is a map: which of the five layers your question is in, and which of the sixty pages answers it.

stable1 example compiled by CIuiovervieworientationguide

Summary#

There is one archetype: the component. A bounded reactive unit with typed props, reactive members, and a declarative render tree. A page, a layout, a dialog, a reusable card — all the same declaration, told apart by attributes on it:

component Card(string title) { … }                 // a reusable piece
[Page("/orders/{id}")] component Order(Guid id) { … }   // …the same thing, with a route
[Composable] component Badge(string text) { … }         // …composable into a public page

Inside one, the four kinds of member cover everything a screen does — and there is no state keyword:

You wantYou write
a value the component ownsa fieldint count = 0;
a value that follows the database, or another valuelive varlive var rows = Order.Where(o => o.Open);
something that happens when the user actsactionaction Add() { count = count + 1; }
a pure helper the tree may callmethod — and render may call it (Calling helpers from render)
what is on the screenrender { } — the tree

And five layers sit under that, which is the other half of the model: almost every question about the UI is really a question about which layer you are in.

LayerWhat it decidesYou write
Themethe app's design values — colour, spacing, radius, typetheme T { Colors { … } }
Stylewhat one box looks like, in a closed vocabulary of propsvariants { base { Bg = Surface; } }
Structurewhat is on the screen and how it is arrangedrender { Stack { Text("Hi"); } }
Behaviourwhat changes, and whenlive var, action, data
Routewhich component is a page, and who may see it[Page("/orders")] [Authorize(…)]

They stack in one direction: a route shows a component, whose structure is built from atoms, each styled by style props, whose values come from theme tokens. Nothing skips a layer, and that is the whole design — change a colour in the theme and every box that named it moves, with no recompile of anything else.

All five layers in one small app — read it top to bottom, and each section below tells you more about the layer you just passed:

// 1. THEME — the values, named once. Every token lowers to a CSS custom property.
theme Studio {
  Colors { Bg = "#F6F7F9"; OnBg = "#16181D"; Surface = "#FFFFFF"; Border = "#E3E6EA"; Accent = "#4F46E5"; }
  Radius { Card = "10px"; }
  FontSize { Body = "15px"; Section = "17px"; }
  FontWeight { Medium = "600"; }
}

// 1b. MOTION — looping, with no destination state, so it is an `animation` and not a `Transition`.
animation Pulse {
  Duration = "2s";
  Easing = EaseInOut;
  Repeat = Infinite;
  0%   { Opacity = 1; }
  50%  { Opacity = 0.5; }
  100% { Opacity = 1; }
}

// 2 + 3. STYLE + STRUCTURE — a `variants` recipe (static CSS) around a render tree built from atoms.
[AllowAnonymous]
component Card(string title) {
  variants {
    // A pseudo-state NESTS inside a variant value — a top-level block here would be a variant DIMENSION, which
    // has to match a parameter.
    base { Bg = Colors.Surface; Rounded = Radius.Card; P = 4; BorderW = 1; Border = Colors.Border; Hover { Border = Colors.Accent; } }
  }
  render {
    Stack(gap: 2) {
      Text(title, fontSize: FontSize.Section, fontWeight: FontWeight.Medium);
      Slot;
    }
  }
}

// 4 + 5. BEHAVIOUR + ROUTE — state an action moves, on a component the router can reach.
[Page("/")]
[AllowAnonymous]
component Home() {
  int count = 0;
  action Add() { count = count + 1; }
  meta { title = "Overview"; }
  render {
    Stack(gap: 4, p: 6, maxW: "640px", mx: "auto") {
      Card("Counter") {
        Row(gap: 3, align: Align.Center) {
          Text("Clicked " + count + " times", fontSize: FontSize.Body);
          Button("Add", onPress: Add);
          Box(w: "10px", h: "10px", rounded: Radius.Card, bg: Colors.Accent, animation: Pulse);
        }
      }
    }
  }
}

Description#

1. Theme names the values, once#

A theme tokens block declares design tokens: Colors, Space, Radius, FontSize, Shadow, Motion, ZIndex, Length, Breakpoints. Each token is a single value, and each lowers to one CSS custom property — which is why re-theming needs no recompile of your components, and why a token can carry a per-mode value (Modes.Of(light: …, dark: …)) that follows the OS dark-mode preference with no flash.

A token can be a whole colour ramp rather than one value — see color palettes. Fonts the app ships are web fonts — shipping a typeface with your app.

A token is a scalar. Anything with internal structure is not a token: that is why keyframes live in animation — looping motion with no destination state rather than in the theme, beside entity and component at the top level.

Ask the compiler, do not guess: osy docs ui-theming is the token vocabulary in full.

2. Style is one closed vocabulary#

style props is the fixed list of style propsBg, P, Rounded, Position, Shrink, Cursor, … — each mapping to CSS. The vocabulary is closed and compile-checked: a misspelled Backgroud = Surface is an error, not a line that silently styles nothing.

You apply them two ways, and they are the same vocabulary either way:

  • variants { } on a component — the recipe. Compiles to static CSS classes, so it costs nothing at runtime and can carry pseudo-states (Hover { }) and responsive overrides (Cozy { }).
  • inline on an atomText("x", fontSize: FontSize.Body, color: Colors.Subtle), for a one-off that does not deserve a component.

Values are a number (a step on a scale — P = 4 is 1rem), a keyword from that prop's closed set (Display = Display.Flex), a theme token by name, or a literal string for props that pass a raw CSS value through.

Reach for a token, not a literal, whenever the value is part of the design. osy lint flags a raw value written three or more times (ui-raw-style-literal-repeated) and names the token to declare — a design decision living in N places is exactly what the theme exists to prevent.

layout primitives is deliberately separate: gap, align and justify are arrangement, not appearance, and take their own path. If you are asking "how do these sit next to each other", that is the layout page, not this one.

Do not derive the vocabulary by reading the renderer. osy docs ui-styling prints every style prop in tables by group; osy docs ui-layout prints gap/align/justify. A prop you invented because it seemed plausible is a compile error at best and a silently ignored line at worst.

3. Structure is atoms, components and controls#

Three kinds of thing render, and knowing which you want answers most "how do I build X" questions:

App-shipped icons, SVG assets and textures are referenced by name and checked at compile time.

osy kit lists every bundled control with its signature and a worked example (osy kit <Control> prints its whole source, which is also how you fork it). osy kit --atoms lists the 16 primitives, each marked container or not — i.e. whether gap/align/justify apply to it at all.

4. Behaviour is what changes, and when#

The reactivity & lifecycle model is the core: var is a snapshot, live var subscribes — a distinction worth knowing before you write anything, because a list that never refreshes is almost always a missing live. on mount / on unmount covers mount/unmount, Pending the in-flight state, skeleton what stands in before the first result lands, and A failing query what happens when a read fails.

Writing data is creating & saving data, and it is the one place UI differs from a server function: an edit applies instantly and stays visible while the user keeps working, and UnitOfWork.Commit() is what sends the accumulated edits to the server atomically. A form commits once, on Save; a page that saves per action (ticking a to-do is the save) commits in each verb. Both are correct — what is never correct is a page with no Save and no UnitOfWork.Commit(), where the write is discarded with no error. A read written inside a body is reading data inside an action; Dialog.Open / Dialog.Ask / Dialog.Confirm / Dialog.Discard is where the unit of work is a choice you make (Inherit vs Root).

Input handling: on change, onEnter, onEscape, debounce, Validation, Clipboard, pointer, keys, drag. Connection is the app's own surface for "the server dropped".

5. A route makes it a page, and decides who may see it#

routes and pages makes a component a page and gives it a URL: [Page("/catalog/{slug}")] captures the segment as a parameter, [Layout(AppShell)] wraps it, and [Render(CSR)] / [Render(SSR)] chooses whether the first response already carries the content. Navigation moves between pages.

page authorization (policies) is the access gate — and the default is the important part: a routed component requires auth unless it says [AllowAnonymous]. Never the other way round. canPress / canEdit / canSee reflects a policy into the UI (a button that disables itself because the rule says so, rather than because someone remembered to check). Session.CurrentUser is who is being shown the page; Visitor is the opaque browser id for someone who has not signed in — a name, never a credential.

Proving a screen works#

A UI test is an ordinary [Test]: Ui — drive the app's UI from a testUi.Visit opens a route, Ui.Click presses what a person would press, Ui.Fill types, and the same Assert.* verbs ask the questions, with your real security rules on. within: is how you address one row when several read alike. osy docs testing-ui is the page; osy docs testing is the model underneath it.

Where things are NOT#

The questions that most often send people to the wrong page:

Read these four first#

If you are starting cold, four pages get you productive and the rest are reference:

  1. component — how to declare one and render it.
  2. layout primitives — how boxes sit next to each other.
  3. theme tokens then style props — in that order: tokens first, then the props that name them.
  4. The reactivity & lifecycle model — the var / live var distinction.

Then routes and pages + page authorization (policies) when you want a real page, creating & saving data the moment it has to save, and control — foreign UI controls (charts, grids, maps) when you need something the atoms cannot express. Writing a component — what differs from C# is worth a skim if you are coming from C#.

The pages#

The whole area, grouped by the question that sends you to it.

The unit itself

Building the tree

Look

Ready-made

Data

Input and events

Route, access, session

Measuring the real box

See also#

Related

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

style props

Inside a `variants` block, each `Name = value` is a style prop from a fixed vocabulary the renderer maps to CSS — paint…

theme tokens

A `theme` block names your app's design tokens — colors, spacing, radii, and more — as reusable values. A token can…

layout primitives

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

routes and pages

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

The reactivity & lifecycle model

How an Osy# component comes alive and stays in sync: declarations are live value bindings, `on mount`/`on unmount` are…

control — foreign UI controls (charts, grids, maps)

A `control` block declares the contract of a foreign UI widget — a chart, a data grid, a map — that a small JavaScript…

Osyrin.Ui (the UI kit)

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

creating & saving data

A UI `action` creates, updates and deletes data by writing `new Entity { … }`, assigning fields, and calling…

Ui — drive the app's UI from a test

Drive the real UI from a test: navigate to a route, click what a person would click, and assert on what the screen…