Components
ArmenusModel renders the dish, works out what this device can show, offers AR when it is genuinely available, and falls back to the poster image when it is not.
<ArmenusModel
item={item}
arLabel="See it on your table"
onEnterAr={(item) => analytics.track("ar_opened", item.id)}
footer={(p) =>
p.ar.blockedOnConversion
? <p>AR is nearly ready — check back in a minute.</p>
: null
}
/>Live
The web component, running. The sliders write into viewSettings— the same field a restaurant edits in the dashboard — so this is the operator’s control surface, not a docs-only affordance.
Live demo not configured
Set NEXT_PUBLIC_ARMENUS_DEMO_KEY to a publishable key for your demo restaurant and this becomes an interactive viewer. Everything else on this page works without it.
Props
The dish. null renders the loading state, so a hook’s data can be passed straight through.
Label on the AR button. Defaults to "View on your table".
Whether a drag rotates the model. Pass false inside a scrolling list — on a phone the two gesture systems fight, and a card that swallows vertical drags makes the whole feed feel stuck.
Rendered beneath the canvas with the resolved capability state, so your copy can adapt to what this particular device can do.
Fires when the user activates AR. For your own analytics.
On web, styles.css is optional and everything is overridable through --armenus-* custom properties. The package ships no utility-class dependency, because it has to work in Tailwind, CSS modules, styled-components and nothing at all.
resolvePresentation()
The capability brain. Given a platform and a model, it returns what to render and whether AR can be offered. Every SDK funnels through it, so the four cannot drift apart. See Formats & platforms for why the rules are not symmetric.
const { inline, ar } = resolvePresentation({
platform: "ios", // "ios" | "android" | "web-other"
model: item.model,
arCoreAvailable: true, // Android — must be probed natively
});
inline.kind // "glb" | "usdz" | "poster" | "none"
inline.url // what to load, when there is something to load
ar.mode // "webxr" | "scene-viewer" | "quick-look" | "none"
ar.supported // can this user place the dish in their room?
ar.reason // why not — written to be shown to a person
ar.blockedOnConversion // only the USDZ is missing; it is comingProbing the device
arCoreAvailable must come from the platform. Defaulting it to true is a real bug: an AR button on a handset without ARCore opens the Play Store instead of the camera, which reads to the user as a broken app.
| Platform | Probe | Why not a version check |
|---|---|---|
| iOS | ARWorldTrackingConfiguration.isSupported | An iPhone 6 on iOS 15 passes a version check and has no world tracking. |
| Android | ArCoreApk.checkAvailability | Answers both “supported device?” and “ARCore installed?” — a version check answers neither. |
| Web | navigator.xr.isSessionSupported | A Permissions-Policyheader can reject the probe outright; treat that as “no”. |
ArmenusModel does all of this on mount. It matters only if you are building on sdk-core directly.
Permissions
The SDKs add no permissions to your app. AR placement runs in the system AR viewer — Quick Look on iOS, Scene Viewer on Android — which is a separate app holding the camera permission in its own process. Your Play Store data-safety declaration and your iOS privacy manifest are unchanged by installing us.
ARKit is linked on iOS only for ARWorldTrackingConfiguration.isSupported and ARQuickLookPreviewItem; no ARSession is ever created. Android never links the ARCore SDK for rendering at all.