UI layer for Atscript
Your types render themselves.
Forms, smart tables and multi-step HTTP flows — declared once in .as, rendered everywhere. No render boilerplate, no manual wiring, no schema drift.
Labels, placeholders, field types and validation live on the model. Mount <AsForm> and you get a fully wired form — submit handler, per-field errors, structured arrays, nested objects.
@meta.label 'Contact'
@ui.submit.text 'Send'
export interface ContactForm {
@meta.label 'Name'
@ui.placeholder 'Jane Doe'
@meta.required
name: string
@meta.label 'Email'
@ui.placeholder 'jane@example.com'
email: string.email
@meta.label 'Message'
@ui.type 'textarea'
message: string
}Server-driven queries, full-text search, sorting, pagination, virtualised scrolling, column resize and reorder — wired up automatically from the same annotated type your DB uses.
@meta.label 'Products'
@db.table 'products'
export interface ProductsTable {
@meta.id
id: number
@meta.label 'Name'
@db.index.fulltext 'search_idx'
@ui.table.width '16em'
name: string
@meta.label 'Price'
@db.amount.currency 'USD'
@ui.table.width '8em'
price: number.decimal
@meta.label 'Status'
status: 'active' | 'draft' | 'archived'
@meta.label 'Updated'
updatedAt: number.timestamp.updated
}<AsTableRoot url="/api/products" v-slot="{ state }">
<AsTable />
</AsTableRoot>| Name | Price ↓ | Status | Updated |
|---|---|---|---|
| Mechanical keyboard | $129.00 | active | 2d ago |
| Trackball mouse | $59.99 | active | 3d ago |
| Aluminium stand | $42.00 | active | 2w ago |
| Braided USB-C cable | $14.50 | draft | 1w ago |
Login + MFA. Sign-up + verify. Invite + register. Long, branching journeys over plain HTTP — the server decides what comes next, the client just renders whatever it gets back.
@Controller()
export class LoginFlow {
@Workflow("auth/login")
@WorkflowSchema([{ id: "creds" }, { id: "mfa" }])
flow() {}
@Step("creds")
async creds(@FormInput input?: LoginForm) {
if (!input) return requireInput(LoginForm);
return { user: await authenticate(input) };
}
@Step("mfa")
async mfa(@FormInput input?: MfaForm, @WfState user) {
if (user.mfaEnabled && !input) return requireInput(MfaForm);
return { finished: true, redirect: "/dashboard" };
}
}<AsWfForm path="/api/wf" name="auth/login" @finished="goHome" />Override vunor's palette, radius, fingertip heights, or icons once — every form input, table cell, dialog and step indicator inherits it. No per-component restyles.
One command teaches Claude Code, Cursor, Windsurf, and Codex the entire UI stack — <AsForm>, <AsTable>, <AsWfForm>, theming, and the framework-agnostic core.
@ui.* annotations & field resolver <AsForm> · <AsTable> · <AsWfForm>moost-wf server workflows & presets One .as file powers TypeScript types, runtime validation, DB schema, REST routes, forms and tables. Three sites, one source of truth.