Skip to main content

Get Started | Fundamentals

The SDK is made up of two parts:

  • a client that manages the connection between your backend and the browser.
  • a list of apps that you pass to the client to serve.
// 1: Import Compose from the SDK
import { Compose, Page, UI } from "@composehq/sdk"

// 2: Define a handler function for your app
const handler = ({ page, ui }: { page: Page, ui: UI }) => {
let count = 0

page.add(() => ui.header(`Count: ${count}`))

page.add(() => ui.button("Increment", {
onClick: () => {
count += 1
page.update()
}
}))
}

// 3: Create an app by passing in the name and a handler function
const counterApp = new Compose.App({
name: "Counter",
handler
});

// 4: Create the client by passing in the API key and the list of apps to serve
const client = new Compose.Client({
apiKey: "<API_KEY_HERE>",
apps: [counterApp]
});

// 5: Initialize the connection to the Compose servers
client.connect();

Then, run your script:

node --watch your_script.js

App structure

Every Compose app is made up of two types of commands.

  1. Page actions do things on the page. For example, use page.add() to render UI components to the page, page.toast() to display a toast notification, or page.confirm() to display a confirmation dialog.
  2. UI components let you quickly construct common UI elements. For example, ui.header() creates a header, ui.table() creates a table, and ui.button() creates a button.

Commands can be combined like legos inside your handler function to quickly build powerful apps with minimal code.

Render UI

Add UI to the page

The page.add() method is the foundational method for adding any sort of UI to the page. You can use it in conjunction with any UI components to quickly build UIs for your apps.

page.add(() => ui.header("Users"))
page.add(() => ui.button("Add user"))

Control the layout

Layout elements like ui.stack() and ui.row() let you group and arrange child components on the page. Compose also offers opinionated layout components like ui.card() and ui.distributedRow() to help you quickly build complex layouts.

page.add(() => ui.card(
ui.distributedRow([
ui.header("Users"),
ui.row([
ui.button("Add"),
ui.button("Edit"),
ui.button("Delete")
])
])
))

Re-render the UI

Easily create reactive apps by updating the UI with page.update(). Compose will diff the previous UI against the new one and intelligently update any components that have changed.

import { Compose, Page, UI } from "@composehq/sdk"

function handler({ page, ui }: { page: Page, ui: UI }) {
let count = 0;

page.add(() => ui.stack([
ui.header(`Count: ${count}`),
ui.button("Increment", {
onClick: () => {
count += 1
page.update()
}
}),
]))
}

const app = new Compose.App({ name: "Counter", handler })
note

When working with objects, you need to reassign the variable itself rather than just modifying a nested property for page.update() to detect the change.

Add interactivity

Input components

Compose offers a comprehensive set of input components that make it easy to collect user input. Every input component offers a hook (either onEnter or onChange) that lets you respond to user interaction.

page.add(() => ui.radioGroup("ice-cream", ["yes", "no"], { 
label: "Do you like ice cream?",
onChange: (value) => page.toast(`You selected: ${value || "nothing"}`)
}))

page.add(() => ui.textInput("name", {
label: "Enter your name",
onEnter: (name) => page.toast(`Hello, ${name || "mysterious person"}!`)
}))
note

Interactive UI components require a unique id as their first argument so Compose can correctly pass user interactions back to the component.

Forms

When you're working with multiple inputs, it's helpful to group them into a form. Forms make it easy to add validation, use submit buttons, and output form values as a single sanitized data structure.

page.add(() => ui.form(
"form-id",
[
ui.radioGroup("ice-cream", ["yes", "no"], {
label: "Do you like ice cream?",
}),
ui.textInput("name", {
label: "Enter your name",
}),
],
{
validate: (form) => {
if (form["ice-cream"] === "no") {
return "You must like ice cream!"
}
},
onSubmit: (form) => {
page.toast(`Hi, ${form.name}.`)
page.toast(form["ice-cream"] === "yes" ? "You like ice cream!" : "You don't like ice cream.")
},
clearOnSubmit: true
}
))

Awaited UI

Normally, page.add() is non-blocking. The UI will render and your script will immediately continue on to the next line.

But, it is possible to await page.add() to block script execution until the included resolve callback is called. This pattern is especially useful for forms.

import { Compose, Page, UI } from "@composehq/sdk"

async function handler({ page, ui }: { page: Page, ui: UI }) {
const userEmail = await page.add(({ resolve }) => ui.form(
"form-id",
[
ui.header("Login to proceed"),
ui.emailInput("email", { label: "Enter your email" }),
ui.passwordInput("password", { label: "Enter your password" }),
],
{
validate: (form) => {
if (form.password !== "compose") {
return "Incorrect password."
}
},
onSubmit: (form) => resolve(form.email)
}
))

page.add(() => ui.text(`Welcome, ${userEmail}!`))
}

const formApp = new Compose.App({ name: "Form", handler })

Next Steps

That's it! We've covered Compose's core concepts in just a couple minutes.

Compose includes a comprehensive library of 40+ powerful UI components, page actions, and more. It scales easily from simple tools to complex, multi-page applications. Refer to the cheat sheet for a quick overview of what's available.

If you ever have any trouble building your app, or want to request a new feature, email the founder at atul@composehq.com.

Now go forth and build something great!