Skip to content

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 ​

bash
npm install fold-page
js
// Standalone — works everywhere, no peer dependencies
import Fold from 'fold-page';
Fold.init();

Build variants ​

VariantImport pathRequires from host
Standalone (default)fold-pageNothing
ESMfold-page/esmreact, three

Use Standalone unless your app already bundles React and Three.js.

Script tag ​

html
<script src="https://cdn.fold.page/latest/fold.standalone.js"></script>
<script>Fold.init();</script>

Fold.init() can be called immediately


Configuration ​

ts
Fold.init({
  foldpage: true,
  key: 'YOUR_KEY',
});
  • foldpage — render the entire <body> in 3D
  • key — 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 ​

ts
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 ​

ts
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) ​

html
<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) ​

js
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:

ts
// 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:

html
<!-- 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:

ts
// 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 });
      });
  }
}
html
<!-- 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 ​

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:

bash
npm install react react-dom three
tsx
import { useEffect } from 'react';
import Fold from 'fold-page/esm';

export function FoldWidget() {
  useEffect(() => {
    Fold.init({ key: 'YOUR_KEY' });
    return () => Fold.destroy();
  }, []);

  return null;
}

Fold.page — 3D UI for the web.