01 / VANILLA TYPESCRIPT
Four small entrances.
Modal, gallery, product details and in-page navigation. Keyboard and no-JS fallback patterns.
Download vanilla starterAPERTURE / THE FIELD GUIDE
Aperture is a reveal primitive. It clips real DOM content using animated SVG geometry, with a native animation driver or optional GSAP adapter.
Three independent projects, each with source, a README and the local library included. Unzip, install, and make it yours. Node 22.12 or newer.
01 / VANILLA TYPESCRIPT
Modal, gallery, product details and in-page navigation. Keyboard and no-JS fallback patterns.
Download vanilla starter02 / REACT
A native dialog with the React hook, live settings, Strict Mode and automatic cleanup.
Download React starter03 / NEXT.JS
Server-rendered pages, a small client boundary, and a reveal before real route navigation.
Download Next.js startercd aperture-react-starter # or vanilla / next npm install npm run dev
Version 0.1.0 is available as a download. An npm registry release will follow later.
Download the packagenpm install ./bolted-aperture-0.1.0.tgz
The content must be a direct child of the container. Set the container's dimensions and keep its padding at zero. Controls that open or close the scene should sit outside the content.
<div class="frame">
<div class="scene">Your content here.</div>
</div>
<button id="reveal">Open / close</button>
<style>
.frame { position: relative; height: 480px; overflow: hidden; }
.scene { background: #d9e2fa; }
</style>
import { createAperture } from '@bolted/aperture';
const reveal = createAperture({
host: document.querySelector('.frame'),
content: document.querySelector('.scene'),
preset: 'folded-iris',
duration: 1.1,
origin: { x: 0.5, y: 0.5 },
colors: ['#2546f0', '#ff6b35'],
});
document.querySelector('#reveal').addEventListener('click', () => {
reveal.toggle();
});
// When your component unmounts: reveal.destroy();
| Method | Behavior |
|---|---|
open(origin?) |
Opens from normalized coordinates or an element's center. |
close() / toggle() |
Interrupts smoothly, continuing from the current progress. |
seek(progress) |
Stops playback and draws any point from 0 to 1. |
setOptions(options) |
Updates geometry immediately; timing changes affect the next animation. |
destroy() |
Removes owned markup, listeners and observers, and restores original inline styles. |
Animation promises resolve with { status, progress }.
Status is completed, interrupted, or
destroyed. A new animation request resolves the previous
one as interrupted.
| Option | Values |
|---|---|
| preset | folded-iris · memory-portal · liquid-lens · record-bloom · editorial-fold |
| duration | Seconds. Default 1.1; close takes 80%; partial travel scales with distance. |
| easing | smooth · gentle · snappy |
| spread / rotation | Fold separation from 0 to 3; additional starting angle in degrees. Circles stay circular. |
| origin | { x, y } in normalized 0–1 coordinates, or an HTMLElement. |
| colors | Two SVG fill colors: outer edge, inner edge. |
| reducedMotion | system (default), or always. The system preference cannot be overridden. |
npm install gsap
import { gsapDriver } from '@bolted/aperture/gsap';
const reveal = createAperture({ host, content, driver: gsapDriver });
The core does not import GSAP. The optional adapter uses GSAP to animate the same progress value, and kills its own tween when interrupted or destroyed.
Aperture hides closed or transitioning content from keyboard interaction and assistive technology. Your app remains responsible for focus, Escape handling, scroll locking, and the meaning of the revealed content. Use native dialogs for modal experiences, and keep a close button available outside the reveal.
Reduced motion settles automatic transitions instantly. Manual scrubbing remains available. Every instance has unique SVG IDs and its own resize observer. Importing during server rendering is safe; create the controller only after mounting.
"use client"; // Next.js interactive boundary
import { useAperture } from '@bolted/aperture/react';
export function Scene() {
const { hostRef, contentRef, aperture } = useAperture({
preset: 'folded-iris', duration: 1.1,
});
return <>
<div ref={hostRef} style={{ height: 480, overflow: 'hidden' }}>
<div ref={contentRef}>Your content.</div>
</div>
<button disabled={!aperture} onClick={() => aperture?.toggle()}>
Open / close
</button>
</>;
}
React is an optional peer. The core does not import it. The hook accepts the core options, handles refs, option updates and cleanup, and exposes the same controller once both elements mount. Conditional mounting and Strict Mode are supported. Animation frames do not trigger component renders.
Use immutable settings. Supplied options patch the controller; omitted settings keep their previous values. Pass explicit values to reset them. Equivalent inline colors and origin coordinates do not redraw; callbacks use the latest committed props. Keep custom driver identity stable. Changing the driver or DOM elements creates a fresh, closed controller.
The React entry point imports safely during server rendering. In
Next.js, put your interactive component behind a
"use client" boundary. Keep ordinary links as fallbacks,
and leave routing to your application. The Next.js starter demonstrates
that pattern. For Vue, use the core in onMounted and clean
up in onUnmounted.
import { getApertureSupport } from '@bolted/aperture';
const { animated, missing } = getApertureSupport();
// Controller mode: 'animated' or 'instant'
Animation requires SVG clipping, native inert, ResizeObserver, animation
frames and motion preference detection. Missing capabilities produce
instant reveals with the same open/close API. No animation driver or SVG
is used in that mode, and closed content stays hidden from interaction.
Instant seek(p) shows content for positive values and hides
it otherwise.
The capability check does not certify rendering. The package uses modern ESM and ES2022 syntax. It cannot provide a fallback in browsers unable to load that code. Physical iPhone 12 Safari testing and a wider browser matrix are still pending.
mode: 'animated' means capabilities are present. Automatic
playback still respects system Reduce Motion and the
always option. Your app owns focus, Escape, scroll locking
and no-JS content; the starters include working patterns and
native-dialog fallbacks.
Choose a transition in the workbench and select Use with AI. Tell us your framework and intended interaction. Copy or download a Markdown brief with the exact recipe, matching setup example, package version and verification steps.
The brief includes the full JSON because assistants fetching a recipe link cannot see its URL fragment. You choose where to paste it. No assistant account or integration is required.
For tools that read documentation directly, llms.txt indexes the Markdown guide, effect catalog and recipe v1 schema. These are also included in the package. This is a discovery convention; each assistant decides what it reads.
import { parseRecipe, recipeToOptions } from '@bolted/aperture/recipes';
const recipe = parseRecipe(jsonText, { strict: true });
const options = recipeToOptions(recipe);
// createAperture({ host, content, ...options });
// useAperture(options);
Strict mode rejects unknown fields. Default imports preserve the existing behavior of dropping them. Recipe bounds match the workbench; the core API permits a wider spread range. The recipe format version and library version are separate. Aperture remains experimental and unpublished; use the matching local archive or starter.
Explore a preset, tune it, and copy the live configuration.
Back to the workbench