Interface
An interface is a contract component — a named, typed construct for concepts that do not render UI, compute derived data, or perform I/O directly. It can represent a data model, a handler shape, a callable contract, or another typed concept the system needs to name explicitly.
An interface component is distinct from the interface block that appears inside a module definition. The module-level interface block declares a module's API — what other modules can call or implement. The interface component is a construct in its own right, with its own props, state, and uses, that lives in the component layer alongside screens, views, providers, and adapters.
Purpose
An interface component gives a name and a shape to concepts that exist in the system but don't perform I/O directly, don't render UI, and don't compute derived data. A Product model, a CatalogueFilter contract, a RouteSwitchHandler callback shape — these are all interface components. They are the typed vocabulary the rest of the system uses.
Because every other component's interface block declares parameter and return types using named constructs, interface components are what give those types their shape. A provider that returns list(Product) depends on Product being defined as an interface component somewhere in the module.
Syntax
The interface keyword is followed by a name in PascalCase, an optional includes clause, an optional description in quotes, and a body in parentheses. The body declares props, optionally state, any methods the contract exposes, and optionally a uses block:
module ProductsModule
interface Product "Represents a single product in the catalogue" (
props (
id(string),
name(string),
price(float),
description(string),
imageUrl(string)
)
)
The module declaration on the line above is the ownership declaration — it binds this interface to its module.
An interface can explicitly include one or more other interfaces. This is the only polymorphism mechanism in WORDS:
module UsersModule
interface AdminUser includes UserIdentity "Represents an administrator account" (
props (
permissions(list(Permission))
)
)
The includes clause is nominal and explicit. AdminUser can be used anywhere UserIdentity is expected because it declares that relationship by name. WORDS does not infer polymorphism from matching property names alone.
When more than one interface is included, the included names are separated by commas:
interface StaffAdmin includes UserIdentity, AuditActor (
props (
permissions(list(Permission))
)
)
props
props declares the typed properties that define the shape of the contract. For a data model, these are the fields — accessible via dot notation anywhere the interface is referenced. For handler-like contracts, these are the values the handler expects:
module ProductsModule
interface Product "Represents a single product in the catalogue" (
props (
id(string),
name(string),
price(float),
description(string),
imageUrl(string)
)
)
A component that receives a Product can access its fields directly — product.name, product.price, product.id — the same way context properties and props properties are accessed elsewhere in the language.
module CartModule
interface CartItem "Represents a single item in the shopping cart" (
props (
productId(string),
name(string),
quantity(integer),
unitPrice(float),
totalPrice(float)
)
)
module CatalogueModule
interface CatalogueFilter "Defines the shape of a filter applied to the catalogue" (
props (
category(?string),
minPrice(?float),
maxPrice(?float),
inStockOnly(boolean) is false
)
)
Polymorphism
Interface polymorphism is expressed with includes. It allows one data interface to declare that it is assignable to another interface:
module UsersModule
interface UserIdentity "Identifies a user anywhere in the system" (
props (
id(string),
fullName(string)
)
)
module UsersModule
interface AdminUser includes UserIdentity "Represents a user with administrative permissions" (
props (
permissions(list(Permission))
)
)
A value of type AdminUser can now be passed wherever UserIdentity is expected:
module UsersModule
view AdminHeader "Displays the active administrator" (
props (
admin(AdminUser)
)
uses (
view UsersModule.UserBadge (
user is props.admin
)
)
)
module UsersModule
view UserBadge "Displays a user identity" (
props (
user(UserIdentity)
)
)
During semantic analysis, assignment is valid when the actual type is the same as the expected type, or when the actual interface includes the expected interface directly or transitively.
includes is deliberately limited to data interfaces — interfaces that declare props only. An interface that declares methods, state, or uses cannot be included and cannot include another interface. This avoids behavioral inheritance, override rules, lifecycle ambiguity, and hidden runtime behavior.
When an interface includes another interface:
- The included interface's props are readable on the including interface.
- Cycles are invalid.
- Duplicate prop names are invalid unless they have the exact same type.
- Cross-module includes use the qualified name, such as
SharedModule.UserIdentity.
WORDS does not support inheritance for modules, states, processes, screens, views, providers, or adapters. Reuse in those constructs is expressed through explicit composition with uses, module contracts, callbacks, and runtime calls.
Methods
An interface component can expose methods directly in its body after props. Methods declare callable behavior — actions the contract can perform or values it can compute — not field access. Field access is handled through props directly via dot notation. Each method is named, lists its parameters if any, and declares a return type if it produces one:
module RoutingModule
interface Router "A callable routing contract" (
props (
basePath(string)
)
navigate path(string)
"Navigates to the given path"
back
"Navigates to the previous path"
getCurrentPath returns(string)
"Returns the current active path"
)
A method that produces no output omits returns. A method that takes no parameters lists only its name and description.
state
state declares private instance-level data the interface component owns and manages internally. It is not accessible from outside — it is exposed only through the methods the component declares:
module OrdersModule
interface OrderDetail "Represents a fully detailed order" (
props (
id(string),
placedAt(integer),
status(string),
total(float)
)
state (
items(list(OrderItem)) is [],
shippingAddress(?ShippingAddress)
)
uses (
adapter system.OrdersModule.OrdersAdapter.loadOrderItems (
orderId is props.id,
onLoad is (
state.items is items
)
),
adapter system.OrdersModule.OrdersAdapter.loadShippingAddress (
orderId is props.id,
onLoad is (
state.shippingAddress is shippingAddress
)
)
)
getItems returns(list(OrderItem))
"Returns the order items once loaded"
getShippingAddress returns(?ShippingAddress)
"Returns the shipping address once loaded"
)
When the adapter method resolves, onLoad fires and receives the adapter's return value. The parameter name inside the callback body is inferred from the adapter method's return type signature. The body assigns it directly into the interface's state using the standard is keyword. The methods getItems and getShippingAddress expose that state to the outside. The consumer calls the methods without any knowledge of how or when the data was fetched — the interface component manages its own lifecycle entirely.
uses
A uses block activates adapters or providers that supply the interface component's internal data. An interface does not perform I/O directly; if it needs external data, it must activate an adapter. Each adapter call declares an onLoad argument whose body fires when the adapter resolves. The parameter name is inferred from the adapter method's return type signature, and the body assigns it directly into state:
onLoad is (
state.<field> is <name>
)
<name>— the identifier inferred from the adapter method's return type, available inside the callback bodystate.<field>— the state field to write intois— the assignment operator, consistent with the rest of the language
A full example:
module ProductsModule
interface ProductDetails "Loads and exposes full product details" (
props (
id(string)
)
state (
reviews(list(ProductReview)) is [],
relatedProducts(list(Product)) is []
)
uses (
adapter system.ProductsModule.ProductsAdapter.loadReviews (
productId is props.id,
onLoad is (
state.reviews is reviews
)
),
adapter system.ProductsModule.ProductsAdapter.loadRelated (
productId is props.id,
onLoad is (
state.relatedProducts is relatedProducts
)
)
)
getReviews returns(list(ProductReview))
"Returns the product reviews once loaded"
getRelatedProducts returns(list(Product))
"Returns the related products once loaded"
)
The parameter name inside the callback body is inferred from the adapter method's return type and is scoped to that body alone.
Module-Level Use
Beyond the component layer, interface plays a role in cross-module communication. A module can declare a handler interface — a contract that other modules implement and register against. When the owning module fires the event, every registered handler is called.
A module declares the handler interface in its own definition:
module RoutingModule (
interface RouteSwitchHandler (
switch path(string) (
if path is "/home"
enter Home "The /home path activates the home screen"
)
)
)
Another module implements it and registers itself:
module ProductsModule (
implements RoutingModule.RouteSwitchHandler (
switch path(string) (
if path is "/products"
enter ProductList "The /products path activates the product list"
)
)
system.RoutingModule.subscribeRoute (
path is "/products",
handler is ProductsModule
)
)
This is the subscription pattern — a module declares the shape of the callback, other modules implement it and register themselves, and the owning module fires the event to all registered handlers. Adding a new module never requires touching an existing one.
Examples
A data model whose fields are accessed via dot notation:
module SessionModule
interface AuthSession "Represents an active session token" (
props (
token(string),
expiresAt(integer),
userId(string)
)
)
A typed error contract:
module AuthModule
interface AuthenticationError "Represents an authentication failure" (
props (
code(string),
reason(string)
)
)
A callable contract that exposes behavior through methods:
module SharedModule
interface Pagination "A callable pagination contract" (
props (
page(integer) is 0,
pageSize(integer) is 20,
total(integer)
)
nextPage
"Advances to the next page"
previousPage
"Goes back to the previous page"
goToPage page(integer)
"Navigates to a specific page"
hasMore returns(boolean)
"Returns true if there are more pages"
)
File Location
Each interface component lives in its own file under the interfaces directory of its owning module:
/my-project/
ProductsModule/
ProductsModule.wds
interfaces/
Product.wds
CatalogueFilter.wds
CartModule/
interfaces/
CartItem.wds
OrdersModule/
interfaces/
OrderDetail.wds
OrderItem.wds
OrderSummary.wds
SharedModule/
interfaces/
Pagination.wds
The file is named after the interface it defines.
Relationship to Other Constructs
An interface component can be used by a state, a screen, a view, a provider, or an adapter. It provides the typed vocabulary — models, handler shapes, callable contracts, and shared data shapes — that all other components reference in their own props and method declarations.
At the module level, interface is also the mechanism through which modules expose APIs and declare subscription contracts. This is a different role from the component — it is the module's boundary, not a construct that lives in the component layer — but both share the same keyword and the same principle: a named, typed shape that the rest of the system can depend on.