The UI model
Reactivity, layout and theming as parts of one language. A breakpoint is a branch, a query is a subscription, and a colour is a token with two values.
01
A theme is a declaration, and dark mode is the second half of each line
Not a second stylesheet, and not a class you toggle. A colour is a token with a light value and a dark one, written together — so they cannot drift, and nothing has to be kept in step.
theme Studio { Colors { 1 Bg = Modes.Of(light: "#F6F7F9", dark: "#0E1116"); OnBg = Modes.Of(light: "#16181D", dark: "#E6EAF0"); Surface = Modes.Of(light: "#FFFFFF", dark: "#171B21"); Border = Modes.Of(light: "#E3E6EA", dark: "#2A2F37"); Subtle = Modes.Of(light: "#6B7280", dark: "#8A94A6"); 2 Accent = "#4F46E5"; OnAccent = "#FFFFFF"; LaneBg = Modes.Of(light: "#EEF0F4", dark: "#1C212A"); } 3 Radius { Card = "12px"; Control = "8px"; Pill = "999px"; } FontSize { Caption = "12px"; Body = "14px"; Section = "16px"; Display = "26px"; } FontWeight { Medium = "600"; } Length { LaneW = "300px"; } 4 Breakpoints { Cozy = 900; } }
Both modes on one line. Adding a dark mode to an app is editing the lines that already exist, not writing a parallel set of them.
A token with no pair is the same in both. The brand colour usually is.
Not just colour. Radii, type sizes, weights and lengths are tokens too, so rounded: Radius.Card is a decision made once.
Your breakpoints, named by you. There is no sm/md/lg vocabulary to learn — and see §3, where this name is used as a branch rather than as a media query.
02
Layout is arguments
There is no class attribute, no utility soup, and no stylesheet that has to agree with the markup. Spacing, colour, radius and size are typed arguments on the element, and every value is a token.
1[Composable] component Column<T>(T[] cards, string title, int count) { render { 2 Stack(gap: 2, p: 2, w: Length.LaneW, bg: Colors.LaneBg, rounded: Radius.Card) { Row(gap: 2, align: Align.Center, px: 1) { Text(title, fontSize: FontSize.Section, fontWeight: FontWeight.Medium); Text(count, fontSize: FontSize.Caption, color: Colors.Subtle, px: 2, py: 1, bg: Colors.Surface, rounded: Radius.Pill); } Stack(gap: 2) { foreach (var c in cards) { 3 Slot(c); } } } } } [Composable] component Board<T>(T[] lanes) { render { 4 if (Layout.AtLeast(Cozy)) { 5 Row(gap: 3, align: Align.Start, overflowX: "auto", pb: 2, role: UiRole.Region, label: "Board") { foreach (var l in lanes) { Slot(l); } } } else { Stack(gap: 3, role: UiRole.Region, label: "Board") { foreach (var l in lanes) { Slot(l); } } } } }
Generic over what it holds. A lane knows it stacks things; it does not know what a card looks like.
Arguments, checked by the compiler. A typo is an error at the position it happened, not a style that silently does nothing.
The caller supplies the child. One lane component serves every board, because the page that uses it decides what goes in.
⭐ A breakpoint is a BRANCH. Wide enough and the lanes are a Row; narrower and they are a Stack. Two structures from one source, with no media query and no second template — and because it is ordinary control flow, a phone can render genuinely different markup rather than the same markup pushed around.
Accessibility is in the declaration, on the element that is the region. UiRole is a real vocabulary the compiler checks, not a raw string a typo breaks silently.
03
A query is a subscription, and state is just a field
The reactivity model is two words long: a plain field is client state, and a live var stays true. There is no store, no selector, no query key and no invalidation.
component BoardPage() { 1 live var lanes = Lane.Include(l => l.Items).OrderBy(l => l.Ordinal); on mount { SeedBoard(); } string moved = ""; action Move(Card card, Lane from) { var next = lanes.Where(l => l.Ordinal > from.Ordinal).FirstOrDefault(); if (next == null) { return; } moved = card.Title + " → " + next.Name; card.Lane = next; } action Save() { UnitOfWork.Commit(); } Card? picked; 2 live var moves = CardFlow.For(picked).Transitions;
Server, and live. An entity query runs on the server under your declared security, and live keeps it true — a card somebody else moves lands here with no poll and no refetch.
The moves available on the selected card, from the WORKFLOW. Not a list the page maintains: the workflow already knows which transitions are legal and who may take them, so the board asks it.
04
Four apps, and nothing new to learn between them
A personal archive, a chat client, an arcade game and the platform's own control plane — five screens between them. Same 46 controls, same 16 atoms, same tokens, same render block — and no two of them look remotely alike. Flip each one to see what drew it.

component NotesPage() { live var notes = Note.OrderByDescending(n => n.ModifiedAt).ToList(); string draft = ""; action Add() { if (draft == "") { return; } var created = CreateNote(draft); draft = ""; Navigation.Go("/notes/" + created.Id); } render { Stack { ScreenHead(eyebrow: notes.Count == 1 ? "One note" : notes.Count + " notes", title: "Notes"); Composer(value: draft, placeholder: "New note", onAdd: Add, disabled: draft == ""); if (notes.Count == 0) { Blank(title: "Nothing written down yet", body: "A note is the fastest way to stop holding something in your head."); } CardGrid { foreach (var n in notes) { RuledRow(title: n.Title, sub: n.Tags == null ? "" : n.Tags, href: "/notes/" + n.Id); } } } }
Markdown field rendered by an atom — read-only here, because a note you are only reading should not pay for the editor.
Stack(maxW: Size.Read, mx: "auto", w: "100%") { Row(align: Align.Center, pb: 5) { Link(href: "/notes") { Text("← Notes", fontFamily: Font.Sans, fontSize: FontSize.Label, letterSpacing: "0.14em", textTransform: TextTransform.Uppercase, color: Colors.TextMuted); } Spacer(); if (note != null) { Link(href: "/notes/" + note.Id + "/edit") { Text("Edit", fontFamily: Font.Sans, fontSize: FontSize.Label, fontWeight: FontWeight.Semibold, letterSpacing: "0.14em", textTransform: TextTransform.Uppercase, color: Colors.Primary); } } } if (note != null) { Stack(gap: 3, pb: 6) { if (note.Tags != null && note.Tags != "") { Eyebrow(text: note.Tags); } Text(note.Title, fontFamily: Font.Display, fontSize: FontSize.Title, color: Colors.OnBg, letterSpacing: "-0.015em", lineHeight: "1.05"); } // The read-only renderer, set as prose: serif, long line height, on the paper rather than in a box. A note // you are only READING should not pay for a megabyte of editing machinery. Box(fontFamily: Font.Text, fontSize: FontSize.Body, color: Colors.OnBg, lineHeight: "1.7") { Markdown(note.Body); }
foreach over a query. The dark mode is the same theme — one token declaration with both values, not a second stylesheet.
foreach (var p in history) { Row(gap: Space.Beat, align: Align.Start) { Avatar(initials: p.Author.DisplayName, size: Size.Sm); Stack(gap: "2px", minW: "0") { Row(gap: Space.Step, align: Align.Center) { Text(p.Author.DisplayName, fontSize: FontSize.Caption, fontWeight: FontWeight.Semibold); Text(p.SentAt.ToString("HH:mm"), fontSize: FontSize.Micro, color: Colors.TextSecondary, fontFamily: Font.Mono); } MessageBody(body: p.Body, handles: handles); } } } foreach (var m in arriving) { Row(gap: Space.Beat, align: Align.Start) { Avatar(initials: m.Author, size: Size.Sm); Stack(gap: "2px", minW: "0") { Row(gap: Space.Step, align: Align.Center) { Text(m.Author, fontSize: FontSize.Caption, fontWeight: FontWeight.Semibold); Text(m.SentAt.ToString("HH:mm"), fontSize: FontSize.Micro, color: Colors.TextSecondary, fontFamily: Font.Mono); } MessageBody(body: m.Body, handles: handles); } } }
Canvas, a camera, a light and a few hundred Draw.Mesh calls a frame. The scene is Osy# — and the whole loop runs in the browser, because that is where its work is.
// A pipe that slid off the left edge respawns at the back of the train — the list never grows. foreach (var p in pipes) { if (p.X < -11.0) { p.X = far + 9.0; far = p.X; p.GapY = 1.9 + (Roll() % 1000) / 1000.0 * 2.6; p.Scored = false; } } } scroll = scroll + speed * dt; if (state == 0) { birdY = 2.6 + Math.Sin(t * 2.2) * 0.16; } if (state == 2) { deadFor = deadFor + dt; vy = vy - 13.5 * dt; birdY = birdY + vy * dt; if (birdY < birdR + 0.04) { birdY = birdR + 0.04; vy = 0; } } // The camera breathes after the bird instead of being bolted to it. camY = camY + (2.4 + birdY * 0.25 - camY) * 5.0 * dt; DrawSky(); Draw.Camera(3.4, camY + 1.3, 17.5, 3.4, camY, 0, 42); Draw.Light(-0.35, -1.0, 0.45, "#fff1d0", "#a8ccff"); // a late-afternoon sun, front-right and high Draw.Fog("#d6ecff", 26, 74); DrawValley(); DrawPipes(); DrawBird(); DrawHud();

DataGrid( label: "Organizations", rows: orgs, columns: [ new GridColumn<Organization> { Name = "Name", Label = "Organization", Value = o => o.Name, Title = true, Fill = true }, new GridColumn<Organization> { Name = "Slug", Label = "Slug", Value = o => o.Slug, Secondary = true }, new GridColumn<Organization> { Name = "Type", Label = "Type", Value = o => o.Type.Label, Width = 140 }, new GridColumn<Organization> { Name = "Status", Label = "Status", Value = o => o.Status.Label, Width = 140 } ], rowSelected: OpenOrg ) { // A CELL TEMPLATE (D78): the Status column renders the app's own Osy#, not the grid's text. The pill is the // kit's Badge — app code, forkable — and the TONE is a presentation decision about the status, so it lives // HERE, in the app's UI layer, never on the enum (metadata must not dictate UI). The label inside the pill // is the enum's own [Label], resolved because `o.Status` is a direct read of an enum-typed field. slot Status { o => Row(align: Align.Center) { if (o.Status == OrganizationStatus.Active) { Badge(tone: Tone.Success) { Text(o.Status); } } else { Badge(tone: Tone.Warning) { Text(o.Status); } } } }
05
What happens when you change it
UI changes, and what they cost
osy get ui/field copies it into ui/lib/, where it shadows the kit's by name. It is ordinary Osy# — the same language your app is written in.Where to go next
Where this page's code runs, and why you did not choose.
63 pages — controls, layout, theming, reactivity.
The app on this page, one command away.