<pc-app>
The <pc-app> tag is the root element for your PlayCanvas application. It is used to initialize the PlayCanvas application and provide a container for your scene.
- It must be a descendant of the document's
bodyelement.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
alpha | Boolean | "true" | Whether the application allocates an alpha channel in the frame buffer, which is what lets the page show through wherever the scene has not drawn |
antialias | Boolean | "true" | Whether the application uses anti-aliasing |
area-light-luts | Asset ID | - | ID of a <pc-asset> holding the area light lookup tables as JSON. Loading it switches area lights on for the whole application, so <pc-light> elements with a rect, disk or sphere shape render as intended; clearing it switches them off again. Applies immediately — see Area Lights |
backend | Enum | "webgpu" | Graphics engine backend: "webgpu" | "webgl2" | "null". WebGPU falls back to WebGL 2 in browsers where it is unavailable — set "webgl2" to force WebGL 2. "null" selects a renderer that draws nothing, and exists for headless testing |
depth-buffer | Boolean | "true" | Whether the application allocates a depth buffer |
loading-bar | Boolean | "true" | Whether the application shows its built-in loading bar while it boots and preloads its assets |
max-pixel-ratio | Number | uncapped | The highest pixel ratio the application renders at, above 0. The canvas is sized by the smaller of this value and the display's own device pixel ratio, so "1" renders at CSS resolution and "2" keeps a dense display sharp without paying for every one of its pixels |
physics-time-scale | Number | "1" | Scale on the time the physics simulation advances by each frame, applied on top of time-scale: below 1 is slow motion, above 1 speeds it up, and "0" pauses physics while the rest of the application keeps running — for a pause menu, say, that must stay interactive while the world stands still |
picking | Enum | "auto" | When the application picks the scene under the pointer to dispatch pointer events on entity elements: "auto" | "always" | "none". auto picks for an event type only while a listener for it is registered on an entity element or on <pc-scene>; always picks for every pointer event, which listeners the application cannot see need, such as one on the document or a framework's delegated handler like React's onPointerMove; none never picks, so entities receive no pointer events. Picking renders the scene again, which is why auto is the default. See When Events Are Dispatched |
stencil-buffer | Boolean | "true" | Whether the application allocates a stencil buffer |
time-scale | Number | "1" | Scale on the time the application advances by each frame. Scripts, animation and physics all advance by the scaled time, so below 1 is slow motion, above 1 speeds it up, and "0" pauses all three together while the scene keeps rendering. To slow down or pause physics alone, use physics-time-scale |
with-credentials | Boolean | "false" | Whether asset requests send credentials (cookies and HTTP authentication) to other origins, which the asset server must allow through CORS. The engine keeps this setting in an HTTP client shared by the whole page, so it applies to every <pc-app> on the page: an app that boots with it switches it on for all of them, and a later change on any app sets it for all of them |
alpha, antialias, backend, depth-buffer and stencil-buffer configure the graphics device,
so they are read once, when the element is inserted into the document and creates it. Changing one
afterwards updates the element's property but has no effect on the running application, and logs a
warning saying so — to apply a new value, remove the element and re-insert it.
Every other attribute is live, applying to the running application as soon as it changes, except
that loading-bar can only remove the bar (see Loading bar). The two time scales
are in place before any script's initialize() runs.
Sizing
The element is sized like a replaced element such as <video> or <img>: a block-level box that
your page's CSS controls, defaulting to the canvas's intrinsic size of 300×150 pixels. The
application's canvas always fills the element, and the drawing buffer resolution follows the
element's size live (capped by max-pixel-ratio) — whatever resizes the element, be it a splitter
drag, a flex reflow or a CSS animation, the rendered scene tracks it.
Fullscreen is not built-in behavior; a full-viewport app is ordinary CSS:
pc-app {
width: 100%;
height: 100vh; /* fallback for browsers without dynamic viewport units */
height: 100dvh;
}
Equally, the element can be embedded at any size — in a card, a split pane or a grid cell — and several apps can coexist on one page.
Size the element with explicit width and height. The library's default styles supply explicit
dimensions, and in CSS box resolution those beat inset stretching — so position: fixed; inset: 0
alone does not stretch the element. (The defaults are declared with
:where() at zero specificity, so any
page rule — however plain — overrides them.)
The buffer follows the display as well as the element. Moving the window to a screen of another
pixel density, or zooming the page, changes the device pixel ratio without necessarily resizing the
element, so the application re-evaluates its pixel ratio and resizes the buffer to match. The ratio
in effect — the smaller of max-pixel-ratio and the display's own — is what
app.graphicsDevice.maxPixelRatio holds, while the element's maxPixelRatio property reports the
cap. Code that manages render quality can assign app.graphicsDevice.maxPixelRatio itself: a
display change then keeps that ratio and only resizes the buffer, until max-pixel-ratio is set
again and takes the device back.
The one time the element's size does not drive the drawing buffer is while an XR session is presenting — the session owns the buffer for its duration.
Loading bar
While the application boots and preloads its assets, <pc-app> shows a loading bar along the top of
the element. Set loading-bar="false" to suppress it (set after boot, it removes the bar at once;
setting it back to "true" has no effect until the element is re-inserted), or theme it with these
CSS custom properties:
| Property | Description |
|---|---|
--pc-loading-bar-color | The color of the filled portion of the bar |
--pc-loading-bar-background | The color of the unfilled track behind it |
--pc-loading-bar-height | The height of the bar |
To build a loading screen of your own instead, suppress the bar and drive it from the element's
progress event and loadProgress property.
Events
Listen to these events using addEventListener() or by assigning an event listener to the oneventname property of this interface.
| Event | Description |
|---|---|
progress | A ProgressEvent fired while the application preloads its assets. loaded and total are asset counts rather than bytes, and an asset that fails to load still counts as loaded. It fires at least once per boot, and the final event always has loaded equal to total. |
error | An ErrorEvent fired when the application cannot boot because no graphics device could be created — WebGL disabled, say, or a blocklisted GPU. message names the backends that were requested and error carries the underlying failure. See below for how to catch it. |
Neither event bubbles, so listen on the element itself.
Handling a Failed Boot
An element that fired error never becomes ready and its app property stays null — in
particular, whenReady('pc-app') never settles (see
Programmatic Access). A page that wants a fallback UI should listen
for the event rather than await readiness. Set the handler as an inline attribute: a failure can be
reported as soon as the library starts, before a module script of your own gets to run, so a
listener added from one can miss it. An attribute is in place from the moment the element is
parsed:
<pc-app onerror="document.getElementById('fallback').hidden = false">
The event covers a device that cannot be created at all. With the default backend, a browser
that offers WebGPU tries it first and falls back to WebGL 2, and if both fail that way, the engine
currently leaves the element waiting without firing error. A fallback that must always appear
can also time out: show it if the element has not become ready after a few seconds.
Removing the element and re-inserting it retries the boot with its current attributes.
The pointer events dispatched on entities bubble up through <pc-app> too, alongside the canvas's own native pointer events. event.isTrusted tells the two apart: it is true for the browser's native events and false for dispatched ones. Whether entity events are dispatched at all is up to picking — see When Events Are Dispatched.
Example
A complete application: a camera, a light and a sphere. Try setting antialias="false" or max-pixel-ratio="1" on the <pc-app> tag:
<pc-app>
<pc-scene>
<pc-entity name="camera" position="0 0 3">
<pc-camera clear-color="#8099e6"></pc-camera>
</pc-entity>
<pc-entity name="light" rotation="45 45 0">
<pc-light></pc-light>
</pc-entity>
<pc-entity name="ball">
<pc-render type="sphere"></pc-render>
</pc-entity>
</pc-scene>
</pc-app>
JavaScript Interface
You can programmatically create and manipulate <pc-app> elements using the AppElement API.
The app property is the running engine AppBase — null until the element is ready — which gives you the scene, the asset registry and the render loop; elementFromEntity() maps an engine entity back to the element that fronts it.
See Also
<pc-scene>— the one scene an app renders<pc-asset>— resources the app preloads before the scene starts<pc-wasm>— modules such as physics that the app loads before it boots- Programmatic Access — waiting for
readyand reachingappfrom JavaScript
Examples: Spinning Cube and Basic Shapes.