Skip to main content

Components

Components are the constructs closest to implementation in a WORDS specification. They are what a state uses — the things that render UI, compute data, perform I/O, and expose behavior to the rest of the system.

There are five component types: screen, view, provider, adapter, and interface. Each has a distinct role and a defined set of rules about what it can access and what it can do. What they share is a common syntax for using, composing, and passing data — described on this page before the individual construct references.


The Component Layer

Components sit below the behavioral layer in the WORDS hierarchy. A state activates them — it does not implement them. The state declares what is used; the component defines what that means.

ConstructRole
screenTop-level UI unit; used by a state; has implicit access to the state's context
viewReusable rendering unit; used by screens or other views; receives all data via props
providerComputes and exposes in-memory derived data; no I/O
adapterThe only construct permitted to perform I/O; the only construct permitted to be async
interfaceNamed typed contracts — models, handler shapes, callable contracts, and shared data shapes

A state can activate screens, adapters, providers, and interface components. It cannot activate a view directly; a view must always have a screen or another view as its component parent.


The uses Block

Every component that activates other components does so inside a uses block. The block lists what is used, one per line. When more than one thing is used, the entries are separated by a comma:

AuthModule/screens/LoginScreen.wds
module AuthModule
screen LoginScreen (
uses (
view AppUIModule.HeaderSection,
view UIModule.LoginForm (
onSubmit is (
state.return (
value is credentials
)
),
onForgotPassword is (
state.return (
value is recoverAccount
)
)
)
)
)

A single entry does not require a parenthesis block:

SessionModule/states/SessionIdle.wds
module SessionModule
state SessionIdle receives ?SessionValidationError (
returns StoredSession
uses adapter SessionAdapter.checkSession
)

The uses block is available to all components and to state.


Passing Arguments

Any component use that passes data uses a parenthesized named-argument block. The argument name comes first, followed by is, followed by the value:

view UIModule.Notification (
type is "warning"
)

When more than one argument is passed, they are separated by a comma inside the block:

view UIModule.Notification (
type is "warning",
message is context.reason
)

All call arguments are named; WORDS does not use positional arguments. The same is keyword is used for comparisons in conditional expressions, where context determines which role it plays.


Conditional Mounting

A component is conditionally mounted using if. The condition evaluates the current state's context — what type it is, or what value a property holds:

if context is AccountDeauthenticated (
view UIModule.Notification (
type is "warning",
message is context.reason
)
)

The negative form uses is not:

if context is not AccountRecovered (
view UIModule.Notification (
type is "error",
message is context.reason
)
)

A property on the context can also be evaluated directly:

if context.status is "pending" (
view UIModule.Notification (
type is "info",
message is "Your request is being processed"
)
)

Conditional blocks can appear inside any component's uses block. Each is evaluated independently. This is how components decide what they use depending on context — the state machine drives the condition, the component responds to it:

AuthModule/screens/LoginScreen.wds
module AuthModule
screen LoginScreen (
uses (
view AppUIModule.HeaderSection,

if context is AccountDeauthenticated (
view UIModule.Notification (
type is "warning",
message is context.reason
)
)

if context is AccountRecovered (
view UIModule.Notification (
type is "success",
message is context.message
)
)

view UIModule.LoginForm (
onSubmit is (
state.return (
value is credentials
)
),
onForgotPassword is (
state.return (
value is recoverAccount
)
)
)
)
)

Iteration

A component iterates over a collection using for ... as. The syntax names the collection, binds the iteration variable with as, then the body in parentheses:

for context.notifications as notification (
view UIModule.NotificationCard (
message is notification.message,
type is notification.type
)
)

The iteration variable is bound with as and is available inside the body block. Each item in the collection produces one instance of the child component.

for ... as can appear inside a uses block alongside other entries:

UIModule/screens/DashboardScreen.wds
module UIModule
screen DashboardScreen (
uses (
view UIModule.PageHeader (
title is "Dashboard"
),

for context.notifications as notification (
view UIModule.NotificationCard (
message is notification.message,
type is notification.type
)
)
)
)

Nesting

Components compose by nesting. A screen uses view components. A view can use further view components. There is no depth limit, but the ownership direction is always the same: a parent passes data and handlers down; a child emits events or calls handlers upward.

UIModule/screens/ProductScreen.wds
module UIModule
screen ProductScreen (
uses (
view UIModule.ProductLayout (
view UIModule.ProductHeader (
title is context.name
),
view UIModule.ProductBody (
view UIModule.ProductDescription (
text is context.description
),
view UIModule.ProductActions (
onAddToCart is (
state.return (
value is cartItem
)
)
)
)
)
)
)

A screen is always the root of a UI component tree. A view must always have a component parent that passes it what it needs — it cannot be used by a state directly.


Data Flow

Data moves through a component tree in one direction:

  • Down — via props, from parent to child
  • Up — via props callbacks, from child to parent
  • Local — via state, owned by the component itself

Every component can declare a state block for instance-level data it owns and manages internally — input values in a view, cached collections in a provider, runtime flags in an adapter. This data does not flow outward. It is the component's own concern.

UIModule/screens/OrderScreen.wds
module UIModule
screen OrderScreen (
uses (
view UIModule.OrderSummary (
items is context.items,
total is context.total,
onConfirm is (
state.return (
value is orderConfirmed
)
)
)
)
)

This unidirectional contract keeps components predictable and reusable. A view that receives everything through props can be used anywhere without depending on how the state was entered.


Referencing Components Across Modules

A component defined in one module can be reused by a component in another. The reference uses the qualified name — the module name followed by a dot and the component name:

view AppUIModule.HeaderSection
view UIModule.Notification (
type is "warning",
message is context.reason
)

This applies to all component types, including interface. An interface defined in one module can be used by a state, a screen, or a view in another module using the same qualified name syntax:

uses interface UIModule.PaginationModel (
page is context.page,
total is context.total
)

Only components that are explicitly exposed by a module can be referenced this way.


File Location

Each component lives in its own file, under a directory named after the component type, inside its owning module's directory:

/my-project/
AuthModule/
AuthModule.wds
states/
Unauthenticated.wds
screens/
LoginScreen.wds
views/
LoginFormSection.wds
adapters/
AuthAdapter.wds
providers/
AuthProvider.wds
interfaces/
AuthHandlerInterface.wds
SystemUserModel.wds

Each file carries a module declaration on the line above the component definition:

AuthModule/screens/LoginScreen.wds
module AuthModule
screen LoginScreen (
...
)

This declaration is not a repeated definition — it is the ownership declaration that binds the component to its module.


The sections that follow cover each component type in detail.