Install Guide
Fold.page is quick to add to any website — a single script tag for plain HTML, or an npm install for JavaScript apps.
Installation
npm package
npm install fold-page// Standalone — works everywhere, no peer dependencies
import Fold from 'fold-page';
Fold.init();Build variants
| Variant | Import path | Requires from host |
|---|---|---|
| Standalone (default) | fold-page | Nothing |
| ESM | fold-page/esm | react, three |
Use Standalone unless your app already bundles React and Three.js.
Script tag
<script src="https://cdn.fold.page/latest/fold.standalone.js"></script>
<script>Fold.init();</script>Fold.init() can be called immediately
Configuration
Fold.init({
foldpage: true,
key: 'YOUR_KEY',
});foldpage— render the entire<body>in 3Dkey— license key for production use, Not required for local development.
For the full list of Fold.init() and Fold.set() options, see the Configuration API →.
Teardown
Fold.destroy();Unmounts Fold.page, releases the WebGL context, and frees all Three.js resources. Calling Fold.init() again after destroy() starts a fresh session.
Refresh
Fold.refresh();Optional. Fold reads the DOM once at init() and doesn't watch for changes on its own — if your own JS mutates rendered content afterward (dynamic text updates, SPA route changes, etc.), call Fold.refresh() to re-sync the 3D scene, without tearing down the WebGL context.
See refresh() → in the Scene API for the full behavior, options, and examples.
Framework examples
Vanilla JavaScript (no bundler)
<script src="https://cdn.fold.page/latest/fold.standalone.js"></script>
<script>
Fold.init({ key: 'YOUR_KEY' });
</script>Vanilla JavaScript (bundler, e.g. Webpack / Vite)
import Fold from 'fold-page';
Fold.init({ key: 'YOUR_KEY' });Angular
Use named imports from fold-page and initialise in ngAfterViewInit (not ngOnInit) so the component DOM is fully rendered before Fold reads it:
// home.component.ts
import { Component, AfterViewInit, OnDestroy } from '@angular/core';
import { init as foldInit, destroy as foldDestroy } from 'fold-page';
@Component({ selector: 'app-home', templateUrl: './home.html' })
export class HomeComponent implements AfterViewInit, OnDestroy {
ngAfterViewInit() { foldInit({ key: 'YOUR_KEY' }); }
ngOnDestroy() { foldDestroy?.(); }
}Angular component elements (<app-hero>, <app-section>, etc.) can carry Fold data attributes directly on the host element. Fold discovers them by attribute, not tag name:
<!-- home.html -->
<div class="foldpage" data-foldpage-view-layout="ring">
<app-nav />
<app-hero data-foldpage-view="0" />
<app-section data-foldpage-view data-foldpage-animate="pulse" data-foldpage-animate-axis="y" />
<app-section data-foldpage-view data-foldpage-shader="plasma" />
</div>The component's inner HTML (<h1>, <p>, <img>, etc.) renders as 3D content normally. The <app-*> element itself is a transparent group container — no 3D mesh is generated for it.
Best practice — persistent Fold.init() with router-driven refresh()
The example above calls foldInit()/foldDestroy() per-component, which works for a single always-mounted component. For an app with multiple routes, that pattern tears down and recreates the WebGL context on every navigation — visible as a flash, and wasted work re-parsing content (like a persistent nav) that never actually changed.
Instead, call Fold.init() once in the root component — outside <router-outlet>, so persistent chrome is scanned once and never torn down — then call Fold.refresh() on every NavigationEnd:
// app.ts
import { Component, Injector, afterNextRender, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
import { filter } from 'rxjs/operators';
import { init as foldInit, refresh as foldRefresh } from 'fold-page';
@Component({ selector: 'app-root', imports: [RouterOutlet], templateUrl: './app.html' })
export class App {
private injector = inject(Injector);
constructor(router: Router) {
// Fold.init() runs exactly once for the app's lifetime — nav/footer live
// outside <router-outlet>, so they're scanned once here and never torn
// down by navigation.
afterNextRender(() => {
foldInit({ key: 'YOUR_KEY', loadingIndicator: true });
});
router.events
.pipe(
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
takeUntilDestroyed()
)
.subscribe(() => {
// Wait for Angular to actually render the new route's DOM into
// <router-outlet> before Fold re-scans it.
afterNextRender(() => {
foldRefresh();
}, { injector: this.injector });
});
}
}<!-- app.html -->
<app-nav /> <!-- persistent chrome — scanned once, never torn down -->
<router-outlet /> <!-- routed content — re-scanned via refresh() on every navigation -->
<app-footer data-foldpage-view />See refresh() in the Scene API for the full API, and the SPA Router Refresh demo for this pattern running live.
Vue
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import Fold from 'fold-page';
onMounted(() => Fold.init({ key: 'YOUR_KEY' }));
onUnmounted(() => Fold.destroy());
</script>React
If your app already bundles React and Three.js, use the ESM variant so they aren't bundled twice:
npm install react react-dom threeimport { useEffect } from 'react';
import Fold from 'fold-page/esm';
export function FoldWidget() {
useEffect(() => {
Fold.init({ key: 'YOUR_KEY' });
return () => Fold.destroy();
}, []);
return null;
}
