Media & avatar

Avatar upload + cropping JS

A .joy-avatar-upload needs to be a <label> wrapping a preview <img> and a hidden <input type="file">. On change, open JoyCropper before showing the result:

<label class="joy-avatar-upload">
    <img src="/avatar.png" alt="">
    <span class="joy-avatar-upload-icon"><i class="ph ph-camera"></i></span>
    <input type="file" accept="image/*" hidden>
</label>
input.addEventListener('change', async (e) => {
    const file = e.target.files[0];
    if (!file) return;
    const blob = await JoyCropper.open(file, { outputSize: 512 });
    if (blob) preview.src = URL.createObjectURL(blob);
});

API details at JavaScript API → JoyCropper.

Camera overlay

Originally built for a facial verification flow. You write the getUserMedia() logic yourself — Joy only styles the guide frame:

ClassGuide shape
.joy-camera-overlaydefault oval, for framing a face from a normal distance
.joy-camera-overlay.is-closeuplarger oval, for asking the user to move closer
.joy-camera-overlay.is-documentwide rectangle, for documents
<div class="joy-camera-box">
    <video autoplay playsinline></video>
    <div class="joy-camera-overlay is-closeup"></div>
</div>
<button type="button" class="joy-btn-capture"><i class="ph ph-camera"></i></button>

Camera access requires HTTPS and explicit user permission — handle denied permission and missing cameras in your own JS, Joy doesn't cover those cases.

News thumbnail

Used inside .joy-news-card (see Marketing blocks) — a standard <img>, no special class:

<img src="/thumbnail.jpg" alt="" class="joy-news-thumb">