Library / SDK
playcanvas/pcui avatar
playcanvas/pcui

pcui: PlayCanvas web UI components with an observer data binding layer

UI component library for web-based tools

793 stars98 forksTypeScriptMIT

At a glance

What is it?
PCUI is the component library behind the PlayCanvas editor, a set of fully styled TypeScript elements for building dense web tools, with a React wrapper and a two way data binding layer built on the Observer package. It suits teams shipping a browser based editor or dashboard that want consistent UI without designing each control, and its binding model is the part worth understanding before you commit.
Who is it for?
Adopt pcui when you are building a dense browser tool in TypeScript and want a consistent set of styled controls plus a binding layer that keeps several widgets in sync without manual event plumbing.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 42 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A component library shaped for editors, not for marketing pages

PCUI is the user interface component library for the PlayCanvas ecosystem. The description calls it a UI component library for web-based tools, and the getting started guide puts it plainly: the library exists to create reliable and visually pleasing interfaces by providing fully styled components that you can use directly on your site.

The stated use cases are deliberately unglamorous. Simple forms are one. The other is building graphical user interfaces for complex web tools, and that second case is the reason the library looks the way it does. A component set aimed at content pipelines and 3D editors needs compact controls that sit well next to a viewport, panels that stack, and consistent spacing, rather than large marketing buttons and hero typography. The components are for the machinery of a tool, not for the page around it.

The audience is therefore engineers who already have a web app with a dense interface, usually in TypeScript, and who would rather not specify a look for every dropdown, tree view and numeric field. The library is MIT licensed, published on npm as `@playcanvas/pcui`, and version 6.1.4 in the package manifest. Its documentation lives in a hosted user manual at developer.playcanvas.com, with an API reference and separate example galleries for the ESM and React forms.

The topic list names react, sass and typescript, which is a fair summary of the stack: TypeScript sources, SASS styling, and a React binding that sits on top of the same components rather than replacing them.

Importing a single element and the stylesheet once

The install is a single npm package:

bash
    npm install @playcanvas/pcui

Each element can then be imported on its own, which matters for bundle size in a tool that loads slowly on first paint. The stylesheet is a separate import and only has to be included once per project, which is a small but real detail when you are assembling the library into an existing application.

javascript
import { Label } from '@playcanvas/pcui';
import '@playcanvas/pcui/styles';

const label = new Label({
    text: 'Hello World'
});
document.body.appendChild(label.dom);

Note the shape of that API. A component is constructed with an options object and exposes its DOM through a `dom` property, which you append yourself. There is no virtual DOM involved in the plain ESM usage, no render function and no JSX. This is a widget library in the older desktop tradition, where a control owns its own element and updates it in place, and it is a good fit for imperative application code.

The package manifest confirms the module shape. The package is `"type": "module"`, the main and module entries both point at `dist/module/src/index.mjs`, and the exports map defines three entry points: the root, `./react` and `./styles`. There is no CommonJS build listed, so a consumer whose bundler still defaults to `require` will need to point it at the ESM output explicitly. The `files` array in the manifest ships `dist`, `types`, the React bundle and its types, and the styles directory.

A separate React entry point rather than React from the root

If you work in React, the components are available through a dedicated subpath rather than the package root:

javascript
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import { TextInput } from '@playcanvas/pcui/react';
import '@playcanvas/pcui/styles';

createRoot(document.body).render(
    <TextInput text='Hello World'/>
);

The `@playcanvas/pcui/react` export resolves to `react/dist/module/src/index.mjs` in the exports map, with its own type declarations under `react/types`. The React layer is not a separate design system. It wraps the same PCUI elements, so a `TextInput` in React and the imperative `TextInput` are the same control under two different call styles.

The source layout explains how that is built. Each component lives in its own folder under `./src/components`, and every folder holds four files with fixed roles: `index.ts` for the PCUI element itself, `style.scss` for the SASS styles, `component.tsx` for the React component that wraps the element, and `component.stories.tsx` for the Storybook entry.

That convention is worth noticing because it tells you where to put a new control. Adding a component means adding a folder with all four files rather than a single file, which is a higher per-component cost but keeps the element, its styles, its React wrapper and its documentation example together. A `react/` directory sits at the top level of the repository, separate from `src/`, which is where the React entry point and its types are built.

Two way binding through Observer and the link method

The most distinctive part of PCUI is its data binding layer, and it is not a prop-drilling solution. The library offers two way binding to a given observer object, so updates made in a component are reflected in the observer's data and distributed out to all other subscribed components.

The mechanism rests on the separate `@playcanvas/observer` package, which is the only runtime dependency in the package manifest. A component declares which binding direction it supports and then links itself to a path in an observer:

javascript
import { Observer } from '@playcanvas/observer';
import { Label, TextInput, BindingObserversToElement, BindingElementToObservers } from '@playcanvas/pcui';
import '@playcanvas/pcui/styles';

// create a new observer for a simple object which contains a text string
const observer = new Observer({
    text: 'Hello World'
});

// create a label which will listen to updates from the observer
const label = new Label({
    binding: new BindingObserversToElement()
});

// link the observer to the label, telling it to use the text variable as its value
label.link(observer, 'text');

The three binding classes you see here are the vocabulary. `BindingObserversToElement` means the component reads from the observer, so it displays the value. `BindingElementToObservers` means the component writes to the observer, so a user edit updates the data. `BindingTwoWay` does both, which lets two inputs sit on the same path and either one update the other.

The React form of the same thing passes the binding and the link as props:

jsx
import { Observer } from '@playcanvas/observer';
import { BindingTwoWay, TextInput } from '@playcanvas/pcui/react';
import '@playcanvas/pcui/styles';

// create a new observer for a simple object which contains a text string
const observer = new Observer({
    text: 'Hello World'
});

// create two text inputs, which can both send and receive updates through the linked observer
const TextInput1 = () => <TextInput binding={new BindingTwoWay()} link={{ observer, path: 'text'} />;
const TextInput2 = () => <TextInput binding={new BindingTwoWay()} link={{ observer, path: 'text'} />;

Two practical implications. The unit of shared state is a path string inside an observer tree, not a framework store, so there is no provider to wrap your app in and no subscription hook to call. And the dependency on `@playcanvas/observer` is not optional or replaceable, which means binding behaviour depends on a package outside this repository.

Four CSS classes carry the entire font system

Font customisation in PCUI is unusually narrow, and it is worth knowing before you design against it. The library uses four CSS classes for fonts across all of its components: `.font-regular`, `.font-bold`, `.font-thin` and `.font-light`. By default all four use the Helvetica Neue stack:

css
    font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif;

Overriding the default means writing your own `font-family` rules against exactly those four classes on your page:

css
.font-regular, .font-bold, .font-thin, .font-light {
    font-family: 'Your Font', sans-serif;
}

That is the entire documented font extension point. There is no token file, no theme object and no configuration option for typography in the getting started guide. If your product needs a type scale with more than four weights, or a variable font with different optical sizes, you are overriding classes by hand and accepting that components you have not seen may reference only those four.

The styling stack otherwise follows the SASS convention: each component owns a `style.scss`, and a `styles/` directory at the top level is published separately so the compiled stylesheet can be imported as `@playcanvas/pcui/styles`. There is a `.stylelintrc.json` in the repository, so the styles are linted, and the `browserslist` field in the manifest targets `>0.2%`, `not dead` and `not op_mini all` in production, with the last single version of Chrome, Firefox and Safari in development.

Taken together, this is a library where you adopt the look or you spend time overriding it. The components are described as fully styled, and that styling is the deliverable rather than a starting point.

Storybook for browsing components and npm run docs for the API reference

Because a component library is only as usable as its catalogue, the project invests in two ways of seeing everything it ships.

The first is Storybook. Running a local instance lets you browse the entire collection of components and test any changes you have made to them. Each component page also includes documentation and lets you exercise the component in all of its configuration states, which is the part that saves time when you are deciding whether a control fits your layout:

bash
    npm install
    npm run storybook

The second is the API reference, built from typedoc into the `docs` folder:

bash
npm run docs

The published API reference is then browsable at api.playcanvas.com/pcui, and the hosted manual at developer.playcanvas.com/user-manual/pcui/ carries the narrative guide. Two separate example galleries exist as well: an ESM examples site and a React storybook, both hosted under playcanvas.github.io/pcui.

The repository also carries a `type-tests/` directory, which suggests the public types are checked by dedicated type-level tests rather than only by compiling the source. Alongside it are `test/`, `examples/` with folders for accessibility, elements and utilities, an `examples/index.html`, an `AGENTS.md`, a `CLAUDE.md` and a `release.sh` script.

For anyone building a custom version of the library rather than consuming the published one, the documented command is `npm run build`, which produces a `dist` directory with your custom build. A separate `BUILDGUIDE.md` covers producing a UMD bundle, which the guide points at specifically for use in a PlayCanvas Editor project, and the build is driven by `rollup.config.mjs`.

Against hand-rolled controls and against a general purpose UI kit

The honest comparison for PCUI is not against another component library but against the two options a team actually chooses between: writing the controls yourself, or adopting a general purpose design system.

Hand-rolled controls cost more than they look. A numeric field with a drag handle, a tree view, a labelled group and a search box each carry small amounts of keyboard handling, focus management and ARIA wiring, and each one is a bug surface. PCUI removes that work and gives you a consistent look immediately, at the price of adopting its visual language and of a dependency on the Observer package for anything that needs shared state.

A general purpose design system is broader by design. Such a kit carries a theming layer, a dark mode, a documented token set and components covering far more surface area than an editor needs. PCUI is narrower and more opinionated: four font classes, a fixed component set aimed at tools, and an ESM only package with three export paths. If your product needs theming across multiple brands, or a marketing site alongside the tool, the difference in effort stops favouring PCUI quickly.

The case where PCUI is the wrong tool is the case where the interface is a handful of ordinary forms on a content site. For that, a lightweight form library with no visual identity attached is less work, and a system built for dense panels and tool chrome would be dead weight.

Editorial conclusion

Adopt pcui when you are building a dense browser tool in TypeScript and want a consistent set of styled controls plus a binding layer that keeps several widgets in sync without manual event plumbing. Do not adopt it as a general purpose site UI kit if you need a different look, since the styling is the product and the font system is limited to four CSS classes, and check first that your bundler can consume the ESM only exports and that the Observer package version you install matches the one pcui expects.

Frequently asked questions

How do I install pcui and add a component?

Run `npm install @playcanvas/pcui`, import the component you need and import the styles once per project. The getting started guide shows `Label` constructed with a `text` option and its `dom` appended to the document.

Does pcui work with React?

Yes. React components are exported from the `@playcanvas/pcui/react` subpath, with `TextInput` shown as the example. The React layer wraps the same PCUI elements rather than providing a separate set.

How does the two way data binding in pcui work?

Components declare a binding such as `BindingObserversToElement`, `BindingElementToObservers` or `BindingTwoWay`, then call `link(observer, 'text')` to bind to a path inside an observer from the `@playcanvas/observer` package. Updates made in one component are then distributed to the others.

Can I use a different font in pcui?

Yes, by writing your own `font-family` rules for `.font-regular`, `.font-bold`, `.font-thin` and `.font-light`, which are the four CSS classes PCUI uses for fonts. The default stack is Helvetica Neue with Arial and Helvetica fallbacks.

How do I browse all the components and read the API reference?

Run `npm install` and `npm run storybook` for a local Storybook that documents each component and lets you test its configuration states. The API reference is built into the `docs` folder with `npm run docs`.

Is pcui still being maintained?

The repository is not archived and the last push was on 2026-09-11. The listed releases include v6.1.4 from 2026-05-14 and v6.1.3 and v6.1.2 from 2026-03-06.

Official sources

  1. License: MIT
  2. playcanvas/pcui on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/playcanvas-pcui.svg)](https://hysenlabs.com/projects/playcanvas-pcui)

Was this useful?