Skip to content

Screens

Screens are UI declarations. They live inside StateView slices and support three levels of abstraction — from pure intent (Studio generates the component) to layout with inline code — plus a full external file reference.

Reusable screen templates with named slots, declared at module level:

layout <Name>
template
<slot-name>+
layout MasterDetail
template
sidebar
main
layout DashboardLayout
template
header
left
right
footer

Declares data and available actions. Studio generates the component.

screen <Name>
data <ReadModel>[[] ] via query <QueryName> [by <param>]
action <CommandName>
[navigate to <ScreenName> [by <param>]]
[label "<text>"]
screen InvoiceList
data InvoiceListReadModel[] via query ListInvoices
action RegisterInvoice
navigate to RegisterInvoiceScreen
action CancelInvoice

Adds named sections, tables, forms, and summary widgets, laid out in a layout template’s slots:

screen InvoiceDetails
layout MasterDetail
sidebar
data InvoiceDetailsReadModel via query GetInvoice by invoiceId
section summary
action CancelInvoice
action ChangeInvoiceStatus
main
section lineItems
table lineItems
column lineNumber label "#"
column description label "Description"
column quantity label "Qty"
column unitPrice label "Unit Price"
on row-click navigate to InvoiceLineDetail by lineNumber

Widgets:

WidgetContents
table <name>column <property> [label "<text>"] rows and on row-click navigate to <Screen> [by <param>]
summary <ReadModel>field <property> label "<text>" rows
title "<text>"A section title

Combines layout templates, structural sections, and inline React/HTML/TypeScript blocks. The surrounding Screenplay context provides the typed data contract; the inline block receives it as Props.

screen InvoiceDashboard
layout DashboardLayout
header
section title
data InvoiceSummaryReadModel via query GetInvoiceSummary
react
```
export default ({ data }: Props) => (
<header className="dashboard-header">
<h1>Invoice Dashboard</h1>
<span className="badge">{data.totalCount} invoices</span>
</header>
);
```
left
section overdue
data OverdueInvoicesReadModel[] via query GetOverdueInvoices
table OverdueInvoicesReadModel
column invoiceNumber label "Invoice #"
column dueDate label "Due Date"
on row-click navigate to InvoiceDetails by invoiceId

A screen binds to things by name — via query All, action RegisterInvoice, navigate to InvoiceDetails. A bare name resolves from the inside out: the slice it is written in, then the enclosing feature, then the module, then the document. The innermost match wins.

That rule exists because a document generated from code cannot make every name unique. Query names come from C# method names, which are unique only per read model — one real application declares 76 queries under 37 distinct names, with All appearing 21 times. Two slices in one feature can each declare All, and each screen gets its own:

module Invoicing
feature Preparation
slice StateView Queue
query All => QueueReadModel
screen QueueScreen
data QueueReadModel[] via query All
slice StateView Deviations
query All => DeviationReadModel
screen DeviationScreen
data DeviationReadModel[] via query All

A slice keeps its own vocabulary, and a name declared next door does not silently take over.

A screen that aggregates read models from several slices — a routine Event Modeling shape — qualifies the name with the scope that holds it:

screen OverviewScreen
data QueueReadModel[] via query Queue.All
data DeviationReadModel[] via query Preparation.Deviations.All

Any trailing part of the scope will do: Queue.All, Preparation.Queue.All, or the whole Invoicing.Preparation.Queue.All. Use the shortest one that is unambiguous.

When a name matches two things equally well

Section titled “When a name matches two things equally well”

If a bare name matches more than one declaration at the same depth — two sibling slices both declaring All, referenced from a third — the compiler warns and names the candidates rather than picking one:

Ambiguous query 'All' - it matches 2 declarations equally well
(Invoicing.Preparation.Queue, Invoicing.Preparation.Deviations); qualify it to say which

Unresolved and ambiguous references are warnings rather than errors, because a name may still resolve to something outside the document. The point is that the gap is visible: before this, a screen could navigate to a screen that did not exist and nothing said so.

Full external implementation — Stage uses the file, the Screenplay contract remains visible to Studio.

screen RegisterInvoiceScreen
file Screens/RegisterInvoiceScreen.tsx
TagUsed for
reactReact/TSX components
typescriptPlain TypeScript
htmlStatic HTML
csharpServer-side logic (validation, reactor bodies, command handlers)