Media component

The media component shows an asset from the CDN in a way that fits its moderation status: the media for allowed assets, a notice and a reveal button for age-restricted ones, and an unavailable notice for blocked and purged ones. Widgets covers installing it, styling and translating.

Show an asset

Pass its project, ID and stored moderation status, or an asset from the API as asset, which also supplies its media type and stored preview (placeholder, average colour and size); props you set win over the asset's values. Pass canView when your app lets this viewer see age-restricted media:

import type { AssetModerationStatus } from "@calmlens/sdk/app";
import { CalmLensMedia } from "@calmlens/sdk/react";

type PostPhotoProps = {
  projectId: string;
  assetId: string;
  status: AssetModerationStatus;
  alt: string;
  /** Width divided by height, which you store when the image is uploaded. */
  aspectRatio: number;
  canView: boolean;
  onReveal: () => void;
};

export function PostPhoto({
  projectId,
  assetId,
  status,
  alt,
  aspectRatio,
  canView,
  onReveal,
}: PostPhotoProps) {
  return (
    <CalmLensMedia
      projectId={projectId}
      assetId={assetId}
      status={status}
      canView={canView}
      onReveal={onReveal}
      alt={alt}
      aspectRatio={aspectRatio}
      widths={[400, 800]}
      sizes="(min-width: 768px) 50vw, 100vw"
    />
  );
}
StatusWhat the component shows
allowed, limited or pendingThe media, with the blurred placeholder until it loads
age-restrictedThe placeholder with a notice and a reveal button until you pass canView, then the media with the allowAgeRestricted opt-in
blocked or purgedAn unavailable notice, without requesting the media
Any status, when an asset's content.status is removedAn unavailable notice, without requesting the media

The reveal button calls onReveal, where your app runs its own check before rendering again with canView; Show sensitive media covers that decision. A media URL that fails to load also shows the unavailable notice.

  • Images come from the asset's rendered resource, so the CDN serves the best format the browser accepts without a list of formats. widths and sizes add a srcset, so browsers fetch the smallest image that fits. Each width is a separate image transform; see Lenses and transforms.
  • lens and transform apply a named lens or a direct transform.
  • aspectRatio (or width and height) keeps the box its final size while the image loads.
  • placeholderHash takes the asset's content.image.thumbhash and draws the placeholder in the browser, with no placeholder request. It also works for private assets and signed URLs.
  • url takes a signed URL from your server instead of building a public CDN URL, for private assets or when CalmLens must refuse age-restricted content to anyone your backend hasn't approved.
  • type is image (default), video or audio. Video and audio play the original file, with no poster or placeholder.

React Native

CalmLensMedia from @calmlens/sdk/react-native shows assets the same way with React Native's Image. React Native has no srcset, so the component measures itself and loads the smallest of widths that covers its width in device pixels. Give it a size with aspectRatio, width and height, or an asset that has them, or a style; otherwise it keeps a minimum height while the image loads.

React Native has no built-in video or audio player. Pass renderPlayer to play them, for example with expo-video; without it, video and audio show only their placeholder:

import { CalmLensMedia, type MediaAsset, type MediaPlayerSource } from "@calmlens/sdk/react-native";
import { useVideoPlayer, VideoView } from "expo-video";
import { StyleSheet } from "react-native";

function Player({ src }: MediaPlayerSource) {
  const player = useVideoPlayer(src);
  return <VideoView player={player} style={StyleSheet.absoluteFill} nativeControls />;
}

export function PostMedia({ asset, alt }: { asset: MediaAsset; alt: string }) {
  return (
    <CalmLensMedia
      asset={asset}
      widths={[320, 640, 1080]}
      alt={alt}
      renderPlayer={(source) => <Player {...source} />}
    />
  );
}

sizes, lazy, fetchPriority, position and controls are web-only and not available in React Native.

Signing in

Signing out

Cookies on CalmLens

We use essential cookies for sign-in and security. With your permission, functional cookies remember your preferences and Google Analytics measures website use and signups. Optional ad measurement cookies link Google ad clicks to signups. Personalised advertising stays off. Manage cookies