Custom UI
The SDK is headless. Use the per-frame callbacks and your own DOM/canvas/3D rendering to build a scan UI that matches your application’s design language exactly.
Architecture
Section titled “Architecture”You handle all visual rendering; the SDK manages capture, processing, and result delivery. The split:
+---------------------+ Callbacks +---------------------+| Your Code | <------------------- | Circadify SDK || | | || - <video> element | onProgress | - Camera access || - Progress ring | onQualityState | - Face detection || - Quality pills | onQualityWarning | - ROI extraction || - Face overlay | onLandmarks | - WASM runtime || - Result display | onCameraReady | - Tensor upload |+---------------------+ +---------------------+ | | | User clicks "Start" | +------- measureVitals() ------------------>| | AbortController.abort() | +------- signal --------------------------->|- Your code — renders the UI, handles user interaction, displays all visual feedback.
- Circadify SDK — manages camera access, face detection, on-device processing, server upload, and result delivery.
- Callbacks — set on the constructor, the SDK reports progress, quality, landmarks, and camera-ready signals through them.
Setting Up
Section titled “Setting Up”import { CircadifySDK } from '@circadify/web-sdk';
const sdk = new CircadifySDK({ apiKey: 'ck_live_your_key_here', onProgress: ({ percent, phase }) => renderProgressRing(percent, phase), onQualityState: (q) => renderQualityPills(q), onQualityWarning: (w) => renderToast(w.message, w.severity), onLandmarks: (lm) => renderFaceOverlay(lm),});Rendering the Camera Feed
Section titled “Rendering the Camera Feed”You own the <video> element. Pass it to measureVitals() as videoElement — the SDK binds the camera stream to it:
<video id="camera-feed" autoplay playsinline muted></video>const videoEl = document.getElementById('camera-feed');
const result = await sdk.measureVitals({ videoElement: videoEl, demographics: { age: 35, sex: 'M' },});You control the video element entirely: size, position, CSS transforms (e.g. transform: scaleX(-1) for mirroring), border styling, overlays, and z-index layering are all yours to define.
Building the UI
Section titled “Building the UI”Here is a complete example tying together a custom camera feed, progress indicator, quality feedback, and results display:
<div class="scan-wrapper"> <video id="camera-feed" autoplay playsinline muted></video> <canvas id="face-overlay"></canvas> <div id="progress-ring"></div> <p id="feedback-message" aria-live="polite"></p> <div id="results-card" hidden></div> <p id="error-message" hidden></p> <button id="start-btn">Start Scan</button> <button id="cancel-btn" hidden>Cancel</button></div>import { CircadifySDK } from '@circadify/web-sdk';
const videoEl = document.getElementById('camera-feed');const overlayEl = document.getElementById('face-overlay');
const sdk = new CircadifySDK({ apiKey: 'ck_live_your_key_here', onProgress: ({ percent, phase }) => { renderProgressRing(percent); renderFeedbackMessage( phase === 'capturing' ? 'Measuring...' : phase === 'processing' ? 'Analyzing...' : 'Hold still...' ); }, onQualityState: (q) => { if (!q.isReady && q.messages.length > 0) { renderFeedbackMessage(q.messages[0]); } }, onLandmarks: (landmarks) => { drawFaceMesh(overlayEl, landmarks); },});
const controller = new AbortController();
document.getElementById('start-btn').addEventListener('click', async () => { document.getElementById('start-btn').hidden = true; document.getElementById('cancel-btn').hidden = false; document.getElementById('error-message').hidden = true;
try { const result = await sdk.measureVitals({ videoElement: videoEl, signal: controller.signal, demographics: { age: 35, sex: 'M' }, });
renderResultsCard(result); } catch (error) { if (error.code !== 'CANCELLED') { renderErrorState(error.message); } } finally { document.getElementById('start-btn').hidden = false; document.getElementById('cancel-btn').hidden = true; }});
document.getElementById('cancel-btn').addEventListener('click', () => { controller.abort();});
function renderProgressRing(percent) { document.getElementById('progress-ring').style.background = `conic-gradient(#3b82f6 ${percent * 3.6}deg, #e5e7eb 0deg)`;}
function renderFeedbackMessage(message) { document.getElementById('feedback-message').textContent = message;}
function renderResultsCard(result) { const card = document.getElementById('results-card'); card.hidden = false; card.innerHTML = ` <p>Heart Rate: ${result.heartRate} BPM</p> <p>SpO2: ${result.spo2 ?? '---'}%</p> <p>Respiratory Rate: ${result.respiratoryRate ?? '---'} breaths/min</p> <p>Confidence: ${(result.confidence * 100).toFixed(0)}%</p> `;}
function renderErrorState(message) { const el = document.getElementById('error-message'); el.textContent = message; el.hidden = false;}
function drawFaceMesh(canvas, landmarks) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'rgba(59, 130, 246, 0.6)'; for (const lm of landmarks) { ctx.beginPath(); ctx.arc(lm.x * canvas.width, lm.y * canvas.height, 1, 0, Math.PI * 2); ctx.fill(); }}Styling Guidelines
Section titled “Styling Guidelines”When building custom capture UIs, follow these guidelines to provide a reliable user experience:
Visual Feedback
Section titled “Visual Feedback”- Show a continuous progress indicator (ring, bar, or percentage) during the scan so users know the measurement is active.
- Drive live quality pills from
onQualityState(lighting, motion, pose, readiness). - Use
onQualityWarningfor transient toast notifications when conditions briefly degrade. - Provide a clear distinction between the “scanning” and “complete” states.
Accessibility
Section titled “Accessibility”- Ensure all interactive elements (start, cancel buttons) are keyboard-accessible and have visible focus indicators.
- Use
aria-live="polite"regions for progress and feedback text so screen readers announce updates without interrupting the user. - Pair color-based status indicators with text labels (do not rely on color alone).
- Manage focus: move focus to the results area on completion, or to the error message on failure.
Responsive Layout
Section titled “Responsive Layout”- Use relative units or
aspect-ratiofor the video container so it scales across screen sizes. - On mobile devices, consider a full-viewport scan view with an overlay for feedback and controls.
- Test on both portrait and landscape orientations.
Error Recovery
Section titled “Error Recovery”- Map SDK error codes to user-friendly messages that explain what went wrong and what to do next.
- For retryable errors (timeouts, processing failures), offer a “Try Again” button that resets the UI and starts a new session.
- For non-retryable errors (permission denied), guide the user to their browser or OS settings.
Next Steps
Section titled “Next Steps”- React Integration — Use pre-built React components instead of building from scratch
- Configuration — Full callback reference
- Events — Detailed event semantics
- Troubleshooting — Common issues and fixes