Screens
Render a product screen from props for each viewport and color scheme.
Define a screen
Export one defineScreen result from an entry module. Use render(props, context) to return the React content of a view.
// specs/account/invoice.mockup.tsx
import { defineScreen } from "@mokly/mokly";
export default defineScreen({
title: "Invoice",
description: "One paid invoice.",
relatedDocs: [],
props: { state: "paid", lines: 3 },
render: (props, context) => (
<main data-viewport={context.viewport}>
<h1>Invoice</h1>
<p>
{props.state}: {props.lines} lines
</p>
</main>
),
variants: [
{ slug: "overdue", title: "Overdue", props: { state: "overdue" } },
],
});Mokly calls this render for mobile and desktop, in each enabled color
scheme. The context contains viewport and colorScheme. A render can ignore
it. Providers and themes belong in your
renderer.
Screen paths
With the default specs root, this screen has path account/invoice.
Its catalogue URL is /view/account/invoice/. The root, directories, and file
name determine the path. The file name ends at its first dot.
Set slug to replace the last segment. Set path to declare a complete path.
Set movedFrom to declare the complete path the entry had before a move.
A file named index.mockup.tsx, or a definition with slug: "index", supplies
its folder’s own page. Several definitions can share one module when their
slugs or declared paths differ. Each export contains one definition.
An array export is an error. Helper functions and ordinary React components
can share the module.
Mokly keeps rendered documents in memory. build --out writes the static
catalogue. Authored modules stay in your source tree.
Props and schemas
Without propSchema, TypeScript derives the render’s prop type from props.
A variant takes a Partial of that type. Mokly infers the runtime schema from
the own props and each variant’s merged props. It excludes non-data values
with a props-not-data warning.
Declare propSchema when a prop needs a fixed type or a restricted set of
values. Write it as const so TypeScript derives the render type from it.
import { defineScreen } from "@mokly/mokly";
export const invoice = defineScreen({
title: "Invoice",
description: "An invoice state.",
relatedDocs: [],
propSchema: {
type: "object",
additionalProperties: false,
required: ["state"],
properties: { state: { enum: ["paid", "overdue"] } },
} as const,
props: { state: "paid" },
render: (props) => <main>{props.state}</main>,
});Props are plain data. A declared schema rejects unknown keys and invalid
values. Every object schema includes additionalProperties: false.
See components for schemas, slots, and controls.
Saved states
A variant declares slug, title, and props. Its props replace top-level
keys of the parent’s props. Nested objects do not merge.
The example’s overdue state has path account/invoice/overdue and keeps
lines: 3. It is a full screen entry with its own views and links.
A screen variant can override description, tags, colorSchemes,
address, rationale, relatedDocs, useCasePaths, and movedFrom.
An omitted list inherits from the parent, except useCasePaths, which defaults
to an empty list. A variant has no path, no render, and no nested variants.
The helper returns one definition that carries the saved states.
Metadata
| Field | Purpose |
|---|---|
title, description, relatedDocs |
Explain the screen. |
tags |
Classify it with unique lowercase kebab-case names. |
address |
Set the address-bar label in the preview. |
rationale |
Explain a design choice. |
useCasePaths |
Name flows that include this screen. |
colorSchemes |
Restrict the enabled schemes; include light. |
controls |
Replace the controls derived from the schema. |
Use ScreenInput or satisfies ScreenInput to check extra top-level fields
in your own wrappers. Fresh variant literals receive excess-key checks.
Runtime validation applies to JavaScript callers too.
Stable scrolling regions
Give an inner scrolling region a stable id when an edit moves it or changes
its text. Use data-mokly-scroll="account-activity" when a product id does
not fit. Use the same unique lowercase kebab-case value in both versions.
Use data-mokly-scroll="off" to keep a region independent.
Keep a render self-contained
Mokly keeps each rendered view until one of its inputs changes. The inputs of
a screen or component are the modules that its entry module imports, directly
or through other modules, your renderer, the class names of their CSS Modules,
the paths and titles of the definitions they import, and the package.json
and tsconfig.json files beside them. An edit renders again only the views
whose inputs changed.
Let a render depend only on those imports, its props, its slots, and its render context:
- Import a setup module, such as one that installs a date mock, from each entry module that needs it. A setup module that only another entry module imports does not reach the cached views of this one.
- Keep files, environment variables, the clock, and random values out of a render. A view that reads them keeps its first result until one of its inputs changes.
Write an HTML screen
Save a .screen.html file inside a configured root. Start with YAML front
matter. Put the screen’s HTML after the closing delimiter.
---
title: Invoice
description: One paid invoice.
tags: [billing]
props: { status: paid, overdue: false }
variants:
- slug: overdue
title: Invoice, overdue
props: { status: overdue, overdue: true }
---
<main>
<h1 id="summary">Invoice: {{status}}</h1>
<p mokly-if="$overdue">Payment is overdue.</p>
<p mokly-viewport="mobile">Compact detail</p>
<p mokly-viewport="desktop">Full detail</p>
<a href="mock:account/billing">Back to billing</a>
</main>With the default specs root, specs/account/invoice.screen.html has the
path account/invoice. Its variant has the path account/invoice/overdue.
The catalogue renders both entries at mobile and desktop. Each entry also
renders in the color schemes it enables.
Name the screen
title is required. description defaults to an empty string. The front
matter can also contain these fields:
| Field | Use |
|---|---|
tags |
Unique lowercase kebab-case tags |
path |
A complete path that replaces the file-derived path |
movedFrom |
The complete path before a move |
rationale |
The reason for the design |
relatedDocs |
Related document paths or labels |
address |
The address shown in the device frame |
useCasePaths |
Flows whose steps use this screen |
colorSchemes |
A subset of the catalogue schemes that includes light |
props, propSchema, variants |
Data, validation, and saved states |
A path segment uses letters, digits, hyphens, and underscores. Paths keep
their case. Two paths that differ only by case conflict. A file named
index.screen.html is its folder’s own screen. Use a path override when
catalogue placement must differ from the source directory.
Use one body for every view
{{name}} substitutes a scalar prop in text or an attribute. A dotted name,
such as {{user.name}}, reads an object property. Mokly escapes substituted
text. There is no raw substitution form. Spaces around the name inside the
braces are allowed, as in {{ name }}.
mokly-if="$name" keeps an element only when the named boolean prop is
true. mokly-viewport="mobile" or "desktop" keeps it in that viewport.
The other attributes remain ordinary HTML. The screen body is a fragment;
it does not contain html, head, body, base, or script.
Read Props and variants for schemas, optional values, and saved state inheritance.
Use shared components
A registered custom element renders its component. Attributes supply data. Children supply declared slots.
<ui-button label="Save" mokly-instance="save" to="account/saved">
<strong>Save changes</strong>
</ui-button>Use a $name attribute value to pass an object or array prop unchanged.
Use $$ to escape a literal leading dollar. Read
Components for registration and named slots.
Build and browse
npx mokly build --strict
npx mokly serveA build validates every view. It fails on errors. Strict mode also fails on
warnings. Screen documents use
static/mokly-generated/account/invoice/index.mobile.html and
index.desktop.html; dark views add .dark before .html.
Styles explains stylesheet rules. Links explains portable catalogue destinations.