Skip to main content

Get Started | Core Concepts

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 }) => {
const users = database.fetchUsers() // do your own business logic
page.add(() => ui.table("users", users)) // call SDK method to render UI
}

// 3: Create an app by passing a unique route key and a handler function
const usersApp = new Compose.App({
route: "users-dashboard",
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: [usersApp]
});

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

Then, run your script:

node --watch your_script.js

App structure

Compose apps are just async functions. Perform your own business logic and call SDK methods to render UI.

There are two types of SDK methods:

  1. Page methods 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 methods 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.button("Add user")
])
))

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({ route: "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.

Collect user input

Input components

Compose offers an extensive set of inputs that make it easy to collect user input.

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

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

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

Forms

You can group inputs into forms to add validation and receive the user input as a single, sanitized object.

function validateForm(form) {
if (form.age < 18 && form.role === "admin") {
return "You must be at least 18 years old to be an admin."
}
}

function submitForm(form) {
createUser(form.email, form.role, form.age)
page.toast("User created successfully!")
}

page.add(() => ui.form(
"form-id",
[
ui.emailInput("email"),
ui.selectBox("role", ["Admin", "User", "Guest"]),
ui.numberInput("age"),
],
{ validate: validateForm, onSubmit: submitForm }
))

Feedback and loading states

Polished applications include feedback and loading states to keep users informed and confident that their actions are being processed.

For this, Compose includes two powerful abstractions: page.toast() for displaying notifications, and page.loading() for displaying a loading spinner.

function refundBilling(userId) {
page.loading(true, { text: "Processing refund...", disableInteraction: true })

try {
await stripe.refundUser(userId)
page.toast("Billing refunded successfully!", { appearance: "success" })
} catch (error) {
page.toast(error.message, { appearance: "error", title: "Error refunding billing" })
} finally {
page.loading(false)
}
}

Tables

Tables are a foundational part of most internal tools. Compose's ui.table() component makes it easy to display, customize, and take action on tabular data.

const companies = [
{ name: "Apple", tier: "Enterprise", headquarters: "Cupertino, CA", arr: 150000 },
{ name: "Asana", tier: "Basic", headquarters: "San Francisco, CA", arr: 12000 },
/* ... */
{ name: "Notion", tier: "Basic", headquarters: "San Francisco, CA", arr: 8000 },
]

ui.table(
"table-key",
companies,
{
label: "Companies",
actions: [{
label: "View Details",
onClick: (row) => page.modal(() => ui.json(row), { title: "Company Details" })
}],
columns: [
"name",
{ key: "tier", format: "tag" },
"headquarters",
{ key: "arr", label: "ARR", format: "currency" }
]
}
)

It's easy to connect your table to forms that enable you to create, update, and delete rows. See the CRUD example app for a complete example.

Charts

Compose includes powerful charts that make it easy to produce visualizations with your data.

const salesData = [
{ month: "Jan", California: 95, Texas: 120, "New York": 310 },
{ month: "Feb", California: 105, Texas: 135, "New York": 290 },
/* ... */
{ month: "Dec", California: 250, Texas: 300, "New York": 520 },
];

page.add(() =>
ui.barChart("sales-chart", salesData, {
group: "month",
series: ["California", "Texas", "New York"],
groupMode: "grouped",
label: "Monthly Sales by Region",
})
);

Integrate with AI tools

If you use Cursor, we highly recommend indexing Compose's docs, then using AI to help you build your apps!

Next Steps

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

Compose includes a comprehensive library of 40+ page methods, UI components, and more. It scales easily from simple tools to complex, multi-page applications. It's useful to refer to the cheat sheet for a quick overview all the available features.

We also recommend joining the Discord community to get help from engineers on the team and other users.

Now go forth and build something great!