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

Reference / UI

Cell template (your own content in a control's cell)

DataGrid(rows: …, columns: […]) { slot <Field> { row => … } // this column renders YOUR content }

A control that paints cells — a grid — writes plain text in each one. A `slot <Field> { row => … }` block on the call replaces that column's cell with your own content: a status pill, an avatar, a link, a button. The control keeps the columns, the sorting and the responsive layout; you decide what a cell looks like.

stable1 example compiled by CIuicontrolsgridauthoring

Summary#

A grid renders each cell as text. That is right for a name or a price, and wrong for a status — which wants a coloured pill — or a person, which wants an avatar.

A cell template is how you take a column over. Inside the control's call, write slot <Field> { row => … } and that column's cells render your content instead of text. Everything else about the grid still works: the column still sorts, the table still becomes cards on a narrow screen, the row still raises its selection event.

Signature#

DataGrid(rows: <list>, columns: [ … ]) {
  slot <Field> { <row> => <content> }     // <Field> is a property of the row; <row> binds that row
}

<Field> names a property of the row type. If it names something that is not a property — a typo, a column you renamed — that is a compile error, not a cell that quietly renders nothing.

Description#

A status pill#

The classic case: the status column should be a pill, tinted by what the status is.

enum OrgStatus {
  Active,
  [Label("Suspended")] Suspended,
}

entity Organization {
  [Required, MaxLength(100)] string Name;
  OrgStatus Status = OrgStatus.Active;
}

class GridColumn { public string Key; public string Label; }

control DataGrid<T> {
  contractVersion "1.0"
  participation headless
  props {
    T[] rows;
    GridColumn[] columns;
  }
  events { rowSelected(T row); }
}

theme Admin {
  Colors {
    Surface1      = "#fbfbfa";
    TextSecondary = "#5f5f5a";
    BgSuccess     = "#eaf3de";
    TextSuccess   = "#3b6d11";
    BgWarning     = "#faeeda";
    TextWarning   = "#854f0b";
  }
  Radius { Pill = "999px"; }
}

enum Tone { Neutral, Success, Warning }

/// The pill itself — an ordinary component. Its label is CONTENT, so it can hold an enum-typed field, which renders
/// as that member's label.
[Composable] component Badge(Tone tone) {
  variants {
    base { Display = Display.InlineFlex; Px = "10px"; Rounded = Radius.Pill; }
    tone {
      Neutral { Bg = Colors.Surface1;  Color = Colors.TextSecondary; }
      Success { Bg = Colors.BgSuccess; Color = Colors.TextSuccess; }
      Warning { Bg = Colors.BgWarning; Color = Colors.TextWarning; }
    }
  }
  render { Row(align: Align.Center) { Slot; } }
}

[Page("/orgs")]
[Render(CSR)]
component OrganizationsPage() {
  var orgs = Organization.ToList();

  render {
    DataGrid(rows: orgs, columns: [
      new GridColumn { Key = "Name", Label = "Organization" },
      new GridColumn { Key = "Status", Label = "Status" }
    ]) {
      slot Status { o =>
        Row(align: Align.Center) {
          if (o.Status == OrgStatus.Active) { Badge(Tone.Success) { Text(o.Status); } }
          else { Badge(Tone.Warning) { Text(o.Status); } }
        }
      }
    }
  }
}

Three things are worth naming, because each is a decision you are making:

  • The pill is yours. It is an ordinary component, not something the grid provides. Restyle it, or replace it with something else entirely, and no control has to know.
  • The colour is a UI decision, so it lives in the UI. Active is green because this screen says so. Nothing about the colour belongs on the enum — an enum is a set of values, not a palette.
  • Text(o.Status) shows the member's label, because it reads an enum-typed field directly. See [Label], [Icon], [Tone] — what a human reads.

What the { o => … } parameter binds to#

{ o => … } binds that row, typed as the row's entity — so o.Status, o.Name and any other property type-check. You can read whatever the row has, not just the column you are rendering:

slot Name { o => Row(gap: 2) { Avatar(o.AvatarUrl); Text(o.Name); } }

Columns you do not template are unchanged#

Only the columns you write a slot for change. Everything else keeps rendering as text, with the alignment and formatting the column declared. Start by templating the one column that needs it.

See also#

Related

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…

Slot (child content)

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

[Label], [Icon], [Tone] — what a human reads

An enum member stores a compact value but shows a human-readable label. [Label("…")] gives a member its label…

component

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