API index
Every function and value of ui and anime, with the type luau-lsp checks it against, read from zgx.d.luau when the site builds.
Each entry is a member of declare ui or declare anime in zgx.d.luau, the definitions zgx check gives luau-lsp, with the comment it carries there. A name links to the page that covers it.
ui
ui.themeThemeApiui.prefers{ reduced_motion: Readable<boolean>, more_contrast: Readable<boolean>, reduced_transparency: Readable<boolean> }- The system's display settings, as CSS's media queries read them (
prefers-reduced-motion,prefers-contrast: more,prefers-reduced-transparency): each follows the system (:get()tracked). The themes' tokens already take them (zero motion durations, stronger borders, opaque glass); read them for the rest. ui.every(ms: number, fn: () -> ...unknown) -> () -> ()- Call
fneverymsmilliseconds on zgx's clock (no frames between); returning false stops it. Returns a function that stops it. It lives as long as what made it: a component, a builder, or the script. ui.after(ms: number, fn: () -> ()) -> () -> ()- Call
fnonce, aftermsmilliseconds; returns a function that cancels it. ui.column(props: ContainerProps?) -> Nodeui.row(props: ContainerProps?) -> Nodeui.div(props: ContainerProps?) -> Nodeui.button(props: ContainerProps?) -> Nodeui.scroll(props: ScrollProps?) -> Nodeui.scroll_x(props: ScrollProps?) -> Nodeui.text(content: Text | TextProps) -> Nodeui.span(content: Text | SpanProps) -> Spanui.rich(props: RichProps) -> Nodeui.image(props: ImageProps) -> Nodeui.svg(props: SvgProps) -> Nodeui.canvas(props: CanvasProps) -> Nodeui.webview(props: WebViewProps) -> Nodeui.web((opts: WebOptions?) -> Web) & ((id: string, opts: WebOptions?) -> Web)ui.web(opts): a page for this run.ui.web(id, opts): the host's page forid, made withoptsthe first time, kept across reloads.ui.terminal(props: TerminalProps) -> Nodeui.term(id: string, opts: TermOptions?) -> Term- The session for
id, started the first time;ui.term(id)without options only looks it up. ui.command(opts: CommandOptions) -> ()ui.shortcut_text(keys: string) -> string- Keys ("cmd+shift+z") as this platform shows them: "⇧⌘Z" on macOS, "Ctrl+Shift+Z" elsewhere (
cmdis Command on macOS, Control elsewhere); a lone modifier ("cmd") as itself, "⌘" or "Ctrl". ui.windowWindowApiui.checkbox(props: CheckboxProps) -> Nodeui.input(props: InputProps) -> Nodeui.textarea(props: InputProps) -> Nodeui.progress(props: ProgressProps) -> Nodeui.slider(props: SliderProps) -> Nodeui.split(props: SplitProps) -> Node- Resizable panes:
ui.split { size = width, Sidebar(), Content() }; the divider drags (no script runs), takes the arrow keys focused, and resets on a double click. ui.segmented(props: SegmentedProps) -> Nodeui.tabs(props: TabsProps) -> Nodeui.tree(props: TreeProps) -> Nodeui.data_grid(props: DataGridProps) -> Nodeui.calendar(props: CalendarProps) -> Nodeui.date_picker(props: DatePickerProps) -> Nodeui.time_field(props: TimeFieldProps) -> Nodeui.combobox(props: ComboboxProps) -> Nodeui.command_palette(props: CommandPaletteProps) -> Nodeui.select(props: SelectProps) -> Nodeui.stepper(props: StepperProps) -> Nodeui.disclosure(props: DisclosureProps) -> Nodeui.accordion(props: AccordionProps) -> Nodeui.section(props: SectionProps) -> Node- An accordion's section:
ui.section { id = "a", title = "Shipping", … }. ui.separator(props: SeparatorProps?) -> Node- A line between groups, across (
vertical = true: down a toolbar). ui.modal(props: ModalProps) -> Nodeui.layer(props: LayerProps) -> Node- Over the window and non-modal (toasts): no focus, no backdrop; the pointer passes through but for what it holds, which stays live while a dialog holds focus. Raised over everything as it opens (
open, a state; open unless given). ui.popover(props: PopoverProps) -> Nodeui.list(props: ListProps) -> Nodeui.show(cond: boolean | Readable<unknown> | (() -> unknown), node: Node, otherwise: Node?) -> Nodenodewhilecondis truthy, elseotherwise(or nothing).ui.switch(value: string | Readable<unknown> | (() -> unknown), cases: { [CaseKey]: Node }, default: Node?) -> Nodeui.each(list: State<{ any }>, build: (item: any, index: number, row: State<any>) -> Node, opts: EachOptions?) -> Node- A row per item of a list state;
buildgets the item and its index (from 1). The item isany: annotate it to check it,function(tab: Tab, i)(Luau infers a generic callback's parameter from its uses, which misfires). A row whose item changes is built again; abuildtaking a third parameter gets the row's item as state instead, kept current, and its row updates in place. ui.presence(value: string | Readable<unknown> | (() -> unknown), build: (value: any) -> Node, opts: PresenceOptions?) -> Node- What
buildmakes of the value, entering and exiting as it changes; nil shows nothing. The value isany, aseach's item is. ui.slot(name: string, props: SlotProps?) -> Node- Where plugins' contributions to
namego. A plugin extension point.contextreaches what is contributed to it: a contributed component gets it asprops.slot(one component, an instance per slot: a panel per pane). ui.contribute(name: string, node: Node) -> ()ui.state<T>(key: string, initial: T) -> State<T>- The script's (or module's) state for
key, kept across reloads. ui.persist<T>(key: string, initial: T) -> State<T>- State saved across runs, in the host's storage (
ScriptHost::app_id). ui.computed<T>(f: () -> T) -> State<T>ui.sorted(list: State<any>, spec: SortedSpec<any> | (() -> SortedSpec<any>)) -> State<any>- A list state's items in order, kept in order as it changes: a write re-places the items it wrote (
byandfiltercalled for those alone), and a new spec (a spec function's reads changed) sorts it all again.byis a field's name (read in Rust) or a function of an item;filterkeeps the items it is true for. (Annotate the view's type:local view: State<{ Row }> = ui.sorted(rows, …).) ui.variants<D>(definition: D) -> (choice: VariantChoice<D, Readable<any> | (() -> any)>) -> Variants- A component's styles by variant (shadcn's
cva):base, then the chosen option of each axis invariants(by name), then the matchingcompoundrules, then the element's own properties; each replaces what it covers (pan earlierpx). Returns a recipe: call it with the options (names, or states or functions choosing them) and give the result to an element asvariants = …. ui.raw<T>(value: T, reason: string) -> T- A style value the design lint lets through, with the reason.
ui.markdown(text: string, opts: MarkdownOptions?) -> { MarkdownBlock }- Markdown parsed into blocks (
t: p, h, code, ul, ol, quote, hr, table, img; runs{ x, b?, i?, c?, s?, a?, f? }). Withkey(a reply's id), text growing under it parses only its new end, and its earlier blocks are the same tables as before. ui.markdown_view(props: MarkdownViewProps) -> Node- Markdown drawn: paragraphs, headings, lists, quotes, tables, and code blocks with a Copy button, in the theme's colours and sizes, selectable. As its text changes it parses only what changed and builds only the blocks that did: a reply streaming in grows by its last block.
ui.effect(f: () -> ...unknown) -> ()ui.on_mount(f: () -> ...unknown) -> ()- Runs once it is mounted: a component instance, or (outside one) the app's interface, again after each reload.
ui.ref() -> Refui.component<P>(build: (props: P) -> Node) -> (props: P) -> NodeCard { title = "…", child }:build(props)runs as each instance mounts; children also arrive asprops.children. Annotatepropswith your own type (and[number]: Childif it takes children).ui.action(name: string, arg: unknown?) -> Action- A host action (
ScriptHost::action) as a handler.argis plain data (what state holds: no functions); Luau cannot say so of a table typed as a record, so it isunknownhere. ui.nullany- JSON's null, as a value that keeps its key in a table (nil drops it):
{ caps = ui.null }reaches Rust as{"caps": null}. ui.on_cleanup(f: () -> ()) -> ()- Runs
fwhen what is being built goes away: a component unmounting, an effect running again, the script reloading. ui.use_context(name: string, default: any?) -> any- In a component's, row's or presence's build: what the nearest
provide = { [name] = … }above it gave, elsedefault. ui.now() -> number- Milliseconds since the epoch, to the microsecond (
os.timecounts whole seconds). Not tracked. ui.object<T>(t: T?) -> Tt(or a new table) marked a JSON object: empty, it sends{}, not[](a Lua table can't tell). An empty object from the host comes marked, so it goes back as one.ui.call_sync(name: string, arg: unknown?) -> any- A host function's value, now (
ScriptHost::function): a read of data the host keeps, so large data is never copied into the script whole. ui.call(name: string, arg: unknown?) -> Promise- Call a host action for its result (
ScriptHost::async_action; a plain action settles at once with nil): a promise of what it returns. ui.task<A...>(f: (A...) -> (), A...) -> Task- Run
f(...)as a task:p:await()inside it waits for a promise without blocking anything else. Its handle cancels it. ui.try<T, A...>(f: (A...) -> ...T, A...) -> (T?, string?)f(...)'s value and nil, or nil and what it raised, as a string a person reads (an error's text, no traceback):pcallwith its error typed. Awaits insidefwork in a task, as throughpcall.ui.resource<T>(load: () -> T, opts: { initial: T }?) -> Resource<T>- What
loadreturns, loaded in a task (it may await), as states:data,loading,errorandstatus. It loads again onreload()and when a state it read before its first await changes; only the last load's answer is kept.initial:datauntil one answers. ui.form(spec: FormSpec) -> Form- A form's fields, checked as they are typed, and its submit with its pending and failure states; controls bind with
field. ui.fetch(url: string, opts: FetchOptions?) -> Promise<Response>- An HTTP request through the host's transport (
ScriptHost::fetcher): a promise of aResponse. A plugin needs the host's grant. ui.clipboard{ read: () -> string?, write: (text: string) -> boolean, read_image: () -> Promise<Bitmap?>, write_image: (image: Bitmap) -> Promise<boolean> }- The clipboard's text (nil if it holds none), and putting text on it; its image as a
Bitmap(nil if it holds none), and putting one on it: promises, as a large image is decoded and encoded off the main thread. ui.open_files(opts: FilePickerOptions?) -> Promise<{ File }>- The files the user picks: a promise of a list of
File(empty if they cancel). ui.save_file(opts: FilePickerOptions?) -> Promise<File?>- Where the user chooses to save: a promise of a
File, or nil. ui.read_file(file: File | string) -> Promise<string>- A file the user picked, read as text off the main thread: a promise.
ui.write_file(file: File | string, text: string) -> Promise<nil>- Write
textto a file the user picked (beside it, then over it). ui.args{ string }- What the app was launched with after its script (
zgx-run app/ notes.txtgives{ "notes.txt" }). ui.on_open(handler: (e: { files: { File }, urls: { string } }) -> ()) -> ()- Files and URLs the system asks the app to open (a file opened with it, a link to it, a second launch's), the launch's included: the files are the user's choice, as a dialog's are (
ui.read_filetakes them). The app's only: a plugin's is an error. ui.set<T>(state: { read set: (self: any, value: T) -> () }, value: T) -> ActionTcomes from the state'sset, so a literal fits a state of a union (ui.set(density, "compact")for"comfortable" | "compact") and anything else is still an error.ui.toggle(state: State<boolean>) -> Actionui.add(state: State<number>, by: number?) -> Actionui.tween(state: State<any>, opts: TweenOptions?) -> Binding- The state's truthiness (or number) drives a 0-to-1 scalar, or
fromtoto. Durations are milliseconds, as inanime. ui.spring(state: State<any>, opts: SpringOptions?) -> Bindingui.loop(opts: LoopOptions?) -> Bindingui.bitmap(w: number, h: number, alpha: (x: number, y: number) -> number) -> Bitmap- White pixels with the alpha
alpha(x, y)gives (0 to 1), for masks. ui.shader(source: string, opts: { fps: number? }?) -> Shader- A WGSL fill:
fn fill(input: FillInput) -> vec4<f32>, with input.p, input.size, input.time, input.scale and input.params (four vec4s). ui.sort<T>(list: { T }, by: string | (item: T) -> any, descending: boolean?) -> { T }- Sorts
listin place, stably, by a field of each item ("price") or a function of it (called once an item); returns it. Use it overtable.sortwith a comparator for long lists: that calls Luau some 1.7 million times for 100,000 rows. Nil keys go last. ui.toast(opts: ToastOptions) -> number- A toast in the window's corner: it goes after
durationms (5000; 7000 a warning; an error stays), or at its ×. Returns its id. ui.dismiss_toast(id: number) -> ()ui.json{ encode: (value: any, opts: { pretty: boolean? }?) -> string, decode: (text: string) -> any }- JSON:
encode(value, { pretty = true })writes what state could hold (lists as arrays, tables as objects,ui.nullas null);decode(text)gives tables the script may change, and fails saying where. ui.number(n: number, opts: NumberOptions?) -> stringnformatted, asIntl.NumberFormatwould:ui.number(1234.5)is "1,234.50",{ style = "percent" }takes a fraction (0.05 is "5%"),{ notation = "compact" }gives "1.2K",{ sign = "always" }"+3".ui.path() -> Pathui.linear(x1: number, y1: number, x2: number, y2: number, colors: { read [number]: Color }) -> Brush
anime
anime.animate((targets: ElementTargets, params: ElementParams) -> Animation) & ((targets: State<any> | { [string]: any }, params: TableParams) -> Animation)anime.createTimeline(params: TimelineParams?) -> Animationanime.createAnimatable(targets: ElementTargets | State<any> | { [string]: any }, params: { [string]: number | Ease | Modifier | Composition | Tween }) -> Animatableanime.createDraggable(target: ElementTargets, params: DraggableParams?) -> Draggableanime.onScroll(params: ScrollParams?) -> ScrollObserveranime.stagger(value: number | string | { read [number]: number | string }, params: StaggerParams?) -> Stagger- A value (5, "1rem") or a range (
{ -100, 100 }). anime.createSpring(params: { mass: number?, stiffness: number?, damping: number?, velocity: number? }?) -> AnimeEaseanime.spring(params: { bounce: number?, duration: number? }?) -> AnimeEase- A spring of that perceived bounce, settling in
durationms. anime.createScope(params: { defaults: Merge<Timing, { [string]: AnimValue }>? }?) -> Scopeanime.eases{ linear: EaseMaker, none: EaseMaker, ["in"]: EaseMaker, out: EaseMaker, inOut: EaseMaker, outIn: EaseMaker, inQuad: EaseMaker, outQuad: EaseMaker, inOutQuad: EaseMaker, outInQuad: EaseMaker, inCubic: EaseMaker, outCubic: EaseMaker, inOutCubic: EaseMaker, outInCubic: EaseMaker, inQuart: EaseMaker, outQuart: EaseMaker, inOutQuart: EaseMaker, outInQuart: EaseMaker, inQuint: EaseMaker, outQuint: EaseMaker, inOutQuint: EaseMaker, outInQuint: EaseMaker, inSine: EaseMaker, outSine: EaseMaker, inOutSine: EaseMaker, outInSine: EaseMaker, inCirc: EaseMaker, outCirc: EaseMaker, inOutCirc: EaseMaker, outInCirc: EaseMaker, inExpo: EaseMaker, outExpo: EaseMaker, inOutExpo: EaseMaker, outInExpo: EaseMaker, inBounce: EaseMaker, outBounce: EaseMaker, inOutBounce: EaseMaker, outInBounce: EaseMaker, inBack: EaseMaker, outBack: EaseMaker, inOutBack: EaseMaker, outInBack: EaseMaker, inElastic: EaseMaker, outElastic: EaseMaker, inOutElastic: EaseMaker, outInElastic: EaseMaker, steps: EaseMaker, cubicBezier: EaseMaker, irregular: EaseMaker }anime.engine{ speed: number, playbackRate: number, -- nil: every display frame. fps: number?, timeUnit: "ms" | "s", read currentTime: number, read paused: boolean, pause: () -> (), resume: () -> () }anime.utils{ set: (targets: ElementTargets | State<any> | { [string]: any }, params: { [string]: AnimValue }) -> (), -- The current value: a number (angles in degrees) or a string. get: (target: ElementTargets | State<any> | { [string]: any }, property: string) -> (number | string)?, -- Stops every animation of the targets. remove: (targets: ElementTargets | State<any> | { [string]: any }) -> (), -- Animated values return to the node's own: all, or the named ones. cleanInlineStyles: (targets: ElementTargets | Animation, props: (string | { string })?) -> (), -- Without decimals, whole bounds give whole numbers. random: (min: number?, max: number?, decimals: number?) -> number, randomPick: <T>(items: { T }) -> T?, shuffle: <T>(items: { T }) -> { T }, -- `round(value, decimals)`; `round(decimals)` is a modifier. round: ((value: number, decimals: number) -> number) & ((decimals: number) -> AnimeModifier), clamp: (value: number, min: number, max: number) -> number, snap: (value: number, step: number) -> number, wrap: (value: number, min: number, max: number) -> number, mapRange: (value: number, inLow: number, inHigh: number, outLow: number, outHigh: number) -> number, lerp: (a: number, b: number, t: number) -> number, -- Frame-rate independent `lerp`: `dt` in ms. damp: (a: number, b: number, dt: number, factor: number) -> number, degToRad: (deg: number) -> number, radToDeg: (rad: number) -> number, roundPad: (value: number, decimals: number) -> string, padStart: (value: string | number, length: number, pad: string?) -> string, padEnd: (value: string | number, length: number, pad: string?) -> string, createSeededRandom: (seed: number?) -> (min: number?, max: number?, decimals: number?) -> number }anime.svg{ -- Animate `x` (or translateX), `y` and `rotate` with these to follow -- the path. createMotionPath: (d: string) -> { translateX: MotionPathValue, translateY: MotionPathValue, rotate: MotionPathValue, x: MotionPathValue, y: MotionPathValue, length: number }, getTotalLength: (d: string) -> number }anime.text{ -- A row of one text per character (`chars`) or word (`words`), each -- with a class to animate; `style` styles each piece, `props` the row. split: (text: string, params: { words: (boolean | { class: string? })?, chars: (boolean | { class: string? })?, class: string?, style: Props?, props: Props? }?) -> Node }