Embed the VisionUploader Component
Drop the Cencori Vision UI into your own React app in five minutes. Drag-drop, format validation, auto-compression, and clear error messages included.
The cencori/react subpath ships a <VisionUploader /> component you can embed directly in your product. Users drag or click to upload an image, the component validates format and size client-side, auto-compresses oversize photos, POSTs to the Vision API, and hands you the result. Zero UI you have to build yourself.
This guide walks you through installing it, wiring it up, and customizing it for common patterns.
What you get
- Drag-and-drop uploader with a preview thumbnail and remove button
- Client-side validation — rejects wrong formats before the request goes out, so users see the error instantly instead of after a round trip
- Auto-compression — oversize JPEG/PNG/WEBP images are downscaled on the client to fit provider limits, with a small "compressed from X to Y" notice
- Clear error banner — network errors, provider errors, and validation errors all render in the same predictable spot
- Format-support banner — shows users which formats and sizes are accepted before they even try
Prerequisites
- A React app (Next.js, Vite, Remix — anything)
- Tailwind CSS in your project (the component uses standard Tailwind classes; no custom design tokens)
lucide-reactinstalled (peer dependency for the icons)npm install cencori
Basic usage
Import from the cencori/react subpath and drop the component into any client component:
'use client';
import { VisionUploader } from 'cencori/react';
export default function UploadPage() {
return (
<VisionUploader
endpoint="https://cencori.com/api/ai/vision"
apiKey={process.env.NEXT_PUBLIC_CENCORI_KEY}
task="describe"
onResult={(result) => console.log(result)}
/>
);
}That's it. The component handles the rest.
Recommended pattern: proxy through your backend
Never ship your Cencori API key to the browser in production. Instead, point the component at your own API route and have it forward the request server-side:
// app/gallery/page.tsx
'use client';
import { VisionUploader } from 'cencori/react';
export default function GalleryPage() {
return (
<VisionUploader
endpoint="/api/vision/describe" // your own route, same origin
task="describe"
onResult={(r) => savePhoto(r)}
/>
);
}// app/api/vision/describe/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const form = await req.formData();
const res = await fetch('https://cencori.com/api/ai/vision/describe', {
method: 'POST',
headers: { CENCORI_API_KEY: process.env.CENCORI_API_KEY! },
body: form,
});
const data = await res.json();
return NextResponse.json(data, { status: res.status });
}Now your Cencori key never leaves the server.
Props reference
Common patterns
Force a specific provider
If you know you always want to run on Gemini for cost, set provider="google" — the client-side validator will then allow HEIC/HEIF and the 20MB limit, matching what the server will accept:
<VisionUploader
endpoint="/api/vision/describe"
task="describe"
provider="google"
model="gemini-2.5-flash"
onResult={handleResult}
/>OCR mode with a receipt scanner
Use the OCR preset for text extraction:
<VisionUploader
endpoint="/api/vision/ocr"
task="ocr"
provider="anthropic" // Claude has strong OCR
onResult={(r) => setExtractedText(r.text)}
/>See the full end-to-end tutorial in Build a Receipt Scanner.
Hide the banner and use your own
The default banner is opinionated. If your design system needs something different, hide it and use <VisionFormatBanner /> separately (or write your own):
import { VisionUploader, VisionFormatBanner } from 'cencori/react';
<div>
<VisionFormatBanner provider="anthropic" className="my-custom-banner" />
<VisionUploader endpoint="/api/vision/ocr" task="ocr" hideBanner />
</div>Custom loading state
The component renders its own "Analyzing…" indicator, but if you want to control the loading state at a higher level in your UI, use onResult and onError as the signal:
const [loading, setLoading] = useState(false);
<VisionUploader
endpoint="/api/vision/analyze"
onResult={(r) => { setLoading(false); handleResult(r); }}
onError={(e) => { setLoading(false); toast.error(e.message); }}
headers={{ 'X-Request-Id': generateId() }}
/>Error handling
The component renders a red banner for any of these:
unsupported_format— user uploaded TIFF, BMP, SVG, etc.image_too_large— file exceeds the provider's max size and auto-compression failed (or is disabled)network_error— the fetch call itself failed- Anything the server returns as a
4xxor5xx
If you want to render errors yourself instead of the built-in banner, pass an onError handler:
<VisionUploader
endpoint="/api/vision/analyze"
onError={(err) => {
if (err.code === 'unsupported_format') {
alert('Please upload a JPEG or PNG.');
} else {
Sentry.captureException(err);
}
}}
/>The built-in banner still renders — the callback fires alongside it. To fully replace the built-in error UI, wrap the component and use CSS to hide the role="alert" block.
Styling
The component uses vanilla Tailwind classes only — no custom design tokens, no CSS-in-JS. All the interactive elements have sensible defaults for both light and dark mode. If you need to override the button style or the drop-zone border, use the className prop for the outer container and target children with descendant selectors.
Bundle size
The component ships as an ES module. Vite/Next.js/Webpack should tree-shake VisionFormatBanner out if you don't use it. Peer dependencies (react, lucide-react) are not bundled — you provide them.
What it doesn't do (yet)
- Streaming — the component makes a single POST and waits for the full response. Streaming SSE support is on the server; the component doesn't yet consume it.
- Multiple file upload — one image at a time. If you need batch processing, wrap it in your own list state.
- Camera capture — the
<input>uses the standardaccept="image/*"— mobile browsers will offer camera as an option, but there's no in-app camera UI.
Where to go next
/docs/ai/endpoints/vision— full Vision API reference/docs/guides/build-a-receipt-scanner— end-to-end tutorial using this component

