Media & effects
Package Box
Interactive 3D package box with flap folding and rising document motion
detent-package-box
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 4
- Wave
- phase-4-cinematic
- Group
- Media & effects
9.9 KB gzip for the class module
Variants
Activate the button under a box (or click the cardboard, or press Escape once it is open) to cut the tape, fold the
flaps and raise the packing slip. The box in this section reserves the full open height, so nothing on the page
moves while it is closed. Both boxes keep the document inert and aria-hidden until they
open, and each user toggle emits one composed detent-open-change with { open }; property
writes emit none.
Recorded states
openrecorded state; no setterclosedrecorded state; no setterexpandedrecorded state; no setterreduced motionrecorded state; no setteractiverecorded state; no setter
Usage
Import
import '/components/package-box/define.js';
// or register it yourself
import { DetentPackageBox } from '/components/package-box/package-box.js';
Attributes
close-labeldefault-openhintinnermax-widthminoropenopen-labelouterpaper-class-namereserve-spacetapetransform
Properties
anglecolorscutdataonOpenChangeouterStyleslip
Slots
defaultflaplabelsymbolstape-half
Events
detent-open-change— { open: boolean }
Example
Runnable example: site/examples/package-box.ts. The snippet below is generated from the catalogued attributes.
<detent-package-box
close-label="Close"
default-open
hint
inner
max-width="460"
minor
open
open-label="Click to open"
outer
paper-class-name=""
reserve-space
tape
transform
></detent-package-box>
Property-only values
const element = document.querySelector('detent-package-box');
if (element !== null) {
// onOpenChange: (open: boolean) => void
element.onOpenChange = () => {};
// slip: PackageSlip
element.slip = undefined;
// colors: PackageBoxColors
element.colors = undefined;
}
Source
The pinned upstream source for this row:
src/components/ui/PackageBox.tsxsha256 12f25d921e82…
Native files
The native module and its artifacts:
src/components/package-box/package-box.tssrc/components/package-box/styles.csssrc/components/package-box/define.tsdocs/components/package-box.mdsite/examples/package-box.tstests/browser/package-box.spec.tsevidence/components/package-box.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
close-label | closeLabel | string | 'Close' | Kebab-case attribute; camelCase property. |
default-open | defaultOpen | boolean | false | Presence semantics: present means true, absent means false. |
hint | hint | boolean | true | Presence semantics: present means true, absent means false. |
inner | — | — | — | Recorded in the catalog as an attribute. |
max-width | maxWidth | number | 460 | Kebab-case attribute; camelCase property. |
minor | — | — | — | Recorded in the catalog as an attribute. |
open | open | boolean | undefined | Presence semantics: present means true, absent means false. |
open-label | openLabel | string | 'Click to open' | Kebab-case attribute; camelCase property. |
outer | — | — | — | Recorded in the catalog as an attribute. |
paper-class-name | paperClassName | string | '' | Kebab-case attribute; camelCase property. |
reserve-space | reserveSpace | boolean | false | Presence semantics: present means true, absent means false. |
tape | tape | boolean | true | Presence semantics: present means true, absent means false. |
transform | — | — | — | Recorded in the catalog as an attribute. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
angle | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
colors | PackageBoxColors | undefined | Property-only: assign from script; not accepted as an attribute. |
cut | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
data | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
onOpenChange | (open: boolean) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
outerStyle | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
slip | PackageSlip | DEFAULT_SLIP | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-open-change | onOpenChange | (open: boolean) => void | Composed, bubbles; fires only for a user action. |
Slots
| Slot | Notes |
|---|---|
default | Consumer content; rendered through a slot, never re-created. |
flap | Consumer content; rendered through a slot, never re-created. |
label | Consumer content; rendered through a slot, never re-created. |
symbols | Consumer content; rendered through a slot, never re-created. |
tape-half | Consumer content; rendered through a slot, never re-created. |
CSS customisation
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- children, label, symbols, flap and tapeHalf React nodes become the default, label, symbols, flap and tape-half slots; label={false} and symbols={false} are expressed by slotting an empty element.
- className is not an attribute; styling uses the documented parts and custom properties.
- onOpenChange becomes the composed detent-open-change event plus an optional property hook; programmatic writes emit nothing.
- slip, colors and outerStyle stay property-only plain data; angle, cut and data are read-only snapshots; inner, outer, transform and minor are presentation attributes with the pinned source defaults.
- PackageBoxDemo is not ported (a page-level demo wrapper); the docs example and preview replace it.
- clamp:false flap slices are clamped to the pinned end angles (-238deg and -212deg) instead of extrapolating.
- WCAG corrections: muted slip ink is opaque #5c5850 (3.4:1 to 6.78:1) and the stamp is #a8431c at full opacity (4.34:1 to 5.77:1).
- hint=false keeps a visible focus target: the hidden controller row becomes visible while focused.
What it does
A 3D cardboard parcel: activating the controller (or clicking the box, or pressing Escape) cuts the tape, folds the flaps and raises a packing slip out of the box.
Upstream behaviour
- Pure CSS 3D cardboard box geometry with authentic grain texture and lighting gradients
- Choreographed master timeline: seam tape cut, sequential flap folding, and smooth paper rise
- Responsive stage scaling that fluidly adapts to any container width via ResizeObserver
- Customizable packing slip with itemization, totals, shipping info, and tactile rubber stamp
- Configurable color tokens for cardboard, tape, paper, typography, and stamp accent
- Full accessibility support with aria-controls, Esc dismiss, and prefers-reduced-motion adaptation
Differences from upstream
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- children, label, symbols, flap and tapeHalf React nodes become the default, label, symbols, flap and tape-half slots; label={false} and symbols={false} are expressed by slotting an empty element.
- className is not an attribute; styling uses the documented parts and custom properties.
- onOpenChange becomes the composed detent-open-change event plus an optional property hook; programmatic writes emit nothing.
- slip, colors and outerStyle stay property-only plain data; angle, cut and data are read-only snapshots; inner, outer, transform and minor are presentation attributes with the pinned source defaults.
- PackageBoxDemo is not ported (a page-level demo wrapper); the docs example and preview replace it.
- clamp:false flap slices are clamped to the pinned end angles (-238deg and -212deg) instead of extrapolating.
- WCAG corrections: muted slip ink is opaque #5c5850 (3.4:1 to 6.78:1) and the stamp is #a8431c at full opacity (4.34:1 to 5.77:1).
- hint=false keeps a visible focus target: the hidden controller row becomes visible while focused.