Alt text helps people understand an image when they use a screen reader or when the image can’t load. The Web Content Accessibility Guidelines (WCAG) 2.2 Success Criterion 1.1.1 requires meaningful non-text content to have a text alternative that serves the same purpose. This requirement makes visual information more perceivable for people who are blind, have low vision, or use other assistive technologies.
In this tutorial, you’ll build a React app that uploads an image to Cloudinary, generates a description with Cloudinary AI, stores that description with the asset, and uses it as the image’s HTML alt attribute. This workflow supports accessible publishing by making text alternatives part of the image lifecycle instead of a task that can be missed before publication. The Cloudinary image accessibility guide explains how text alternatives, centralized metadata, and AI captioning work together.
- Live Demo: https://react-cloudinary-alt-text.vercel.app/
- Source Code: https://github.com/musebe/react-cloudinary-alt-text
TL;DR: Cloudinary AI Content Analysis generates a caption during upload. An upload preset saves that caption in Cloudinary’s built-in alt contextual metadata field. A server API reads the image and metadata from Cloudinary. React then renders both through AdvancedImage.
TL;DR: Cloudinary AI Content Analysis generates a caption during upload. An upload preset saves that caption in Cloudinary’s built-in alt contextual metadata field. A server API reads the image and metadata from Cloudinary. React then renders both through AdvancedImage.
Altelier is a small accessibility studio for blog images. It demonstrates the complete lifecycle of accessible media:
- A visitor uploads an image with the Cloudinary Upload Widget.
- Cloudinary stores the original in the altelier-blog asset folder.
- Cloudinary AI Content Analysis generates a caption.
- An upload preset saves the caption as context.custom.alt.
- The app reads the stored value through the Cloudinary Admin API.
- React renders the image with the saved description.
- An accessibility library identifies images with missing alt text.
The important design choice is that the image and its description share one source of truth. Cloudinary stores both, so another website, mobile app, or content management system can reuse the same reviewed description.
Cloudinary recommends its built-in alt contextual metadata field for centralized text alternatives. Read more in the Cloudinary image accessibility guide.
An HTML alt attribute exists on one rendered page. Cloudinary contextual metadata belongs to the asset itself. If an editor improves the description in Cloudinary, every connected application can request the updated value instead of maintaining separate copies.
Cloudinary doesn’t automatically insert metadata into every HTML page. Your application still decides whether an image is informative, functional, or decorative. It then maps the stored value to the correct alt attribute during rendering.
Before starting, make sure you have:
- Node.js 20.19 or newer.
- A free Cloudinary account.
- Cloudinary AI Content Analysis enabled for your product environment.
- Basic familiarity with React and TypeScript.
This project uses React, Vite, shadcn/ui, the Cloudinary Upload Widget, the Cloudinary React SDK, and the Cloudinary Node.js SDK.
Start with Cloudinary’s React project generator, then initialize shadcn/ui:
Install the project dependencies and start the local app:
The repository also uses Cloudinary’s agent resources during development. You can learn how to connect them in the Cloudinary MCP servers and skills documentation.
An upload preset is a saved group of upload rules. The browser sends its preset name with each upload, and Cloudinary applies the configured folder, formats, analysis, and metadata behavior.
Open Cloudinary Console → Settings → Upload → Upload presets, then select Add upload preset.
Use these settings in the General tab:
The React app sends the preset name instead of sending every upload option from the browser. Cloudinary looks up the preset and enforces its settings. This keeps folder placement, captioning, file restrictions, and metadata behavior consistent across uploads.
The demo uses an unsigned preset because the browser uploads directly through the Cloudinary Upload Widget. The browser needs only the cloud name and preset name. It never receives the Cloudinary API secret.
An unsigned preset works well for a public demo with strict format and size limits. Its name remains visible in browser code, so another person could reuse it to upload unwanted files.
Use a signed upload when your application must:
- Require an authenticated account before upload.
- Set sensitive upload options on the server.
- Apply per-user folder or access rules.
- Prevent anyone outside your application from reusing the upload configuration.
Signed uploads require a server endpoint that creates a short-lived signature. The rest of this tutorial uses an unsigned preset to keep the accessibility workflow focused.
In Optimize and Deliver, set Allowed formats to:
Leave Incoming transformation, Eager transformations, and Format blank. This preserves the original upload. Cloudinary can create optimized delivery variants when a browser requests them.
Open the preset’s Addons tab. Under AI media processing, find Cloudinary AI Content Analysis and enable Add AI captioning to your image.
If the option is unavailable, subscribe to the add-on from your Cloudinary account first. The Cloudinary AI Content Analysis documentation explains registration and supported analysis features.
During upload, the preset requests AI captioning from Cloudinary AI Content Analysis. The service analyzes the visible subjects, setting, and actions, then adds a natural-language caption to the upload information. Cloudinary’s Digital Asset Management add-on guide explains where generated captions appear in the Media Library.
Captioning during upload works best when new assets should enter your library with a first draft description. If you already have a large Media Library, use an API update or a Cloudinary MediaFlows captioning workflow to process existing assets instead.
AI captions are a starting point, not a replacement for editorial review. A useful alt description depends on the image’s purpose and surrounding content. Mark decorative images with an empty alt="" instead of describing them.
A caption describes visible content. Alt text communicates the image’s purpose in its current context. Those goals overlap, but they are not identical.
For example, AI might produce “A person holding a red umbrella outside a train station.” That description can work in a travel article. If the image is a link to weather alerts, the useful alternative may be “View current weather alerts.” Treat the generated caption as a draft that an editor can keep, shorten, or replace.
Generating a caption is only half of the workflow. The description must remain available after the upload response disappears.
Open the preset’s Advanced tab and add this On success script:
This script performs two jobs:
- It writes the caption to Cloudinary’s built-in Description (alt) field.
- It adds the autogenerated-alt tag for filtering and review.
e.upload_info contains the completed upload response. The script follows the response path to captioning.data.caption, checks that text exists, and calls current_asset.update(). Cloudinary then stores the description and tag on the same asset.
The Upload Widget callback exists only in the current browser session. Refreshing the page removes that JavaScript state. Saving the caption as contextual metadata makes it available through the Media Library, Admin API, Search API, and future applications.
The condition also protects the workflow. Cloudinary adds the autogenerated-alt tag only when captioning returns text. The tag records how the description was created, not whether a person approved it. Editors can use it as a review queue instead of treating every upload as successfully reviewed.
Save the preset. The complete console configuration is recorded in the project’s alt-text workflow guide.
Create .env from .env.example, then add these values:
The two VITE_ values are browser-safe. The Upload Widget needs the cloud name and unsigned preset name.
The API key and secret authenticate Admin API requests. Keep them on the server, exclude .env from Git, and never prefix the secret with VITE_. ALTELIER_DELETE_TOKEN protects the demo’s destructive gallery action. It is not a Cloudinary credential.
The Cloudinary Node.js SDK guide covers both environment-based and code-based configuration.
Uploading with an unsigned preset is a browser operation. Searching private asset metadata, reading folder contents, and deleting assets are trusted server operations. Vite includes every VITE_ variable in the browser bundle, which means visitors can inspect those values.
Keep CLOUDINARY_API_SECRET on the Express server or Vercel Function. React calls your /api routes, and those routes call Cloudinary. This boundary lets the interface use Cloudinary management features without exposing account credentials.
The Upload Widget accepts local files, camera captures, and remote URLs. The main configuration needs the cloud name and preset:
The widget emits events for opening, queuing, uploading, success, and failure. Handle only the success event as a completed asset. Its response contains the public_id, dimensions, format, delivery URL, and captioning information.
Altelier keeps the public_id because it is the stable input for Cloudinary delivery transformations. The interface then requests the asset metadata from the server. This second request confirms that Cloudinary persisted both the description and tag before the app reports success.
See the complete UploadWidget.tsx implementation: (https://github.com/musebe/react-cloudinary-alt-text/blob/main/src/cloudinary/UploadWidget.tsx
See the complete UploadWidget.tsx implementation: (
Browser code must not call the authenticated Admin API. Altelier uses a small Express API with the Cloudinary Node.js SDK.
The server searches only the altelier-blog folder and requests the fields needed by the interface:
Cloudinary’s Search API supports folder, tag, contextual metadata, format, dimension, and date expressions. Altelier sorts by creation time so the newest four images appear first.
Use the Search API when you need a collection, such as every image in a folder or every asset missing metadata. Use the Admin API resource method when you already know one public_id and want to verify that asset.
Altelier uses both approaches. The gallery searches the folder, while the post-upload check requests one asset. Both calls run on the server because they require authenticated Cloudinary management access.
The upload response and Search API can expose contextual metadata in slightly different shapes. The server accepts both:
See all API routes, folder checks, and deletion safeguards: https://github.com/musebe/react-cloudinary-alt-text/blob/main/server/app.ts
See all API routes, folder checks, and deletion safeguards:
The Cloudinary React SDK builds a delivery image from its public_id. c_fit keeps the full image visible inside the requested bounds. f_auto selects a format supported by the browser, while q_auto balances visual quality and file size.
The responsive() plugin requests an image size suited to the rendered width. The blurred placeholder improves the loading state, and explicit dimensions reduce layout movement.
See the React image transformation guide and Cloudinary image optimization guide.
See the React image transformation guide and Cloudinary image optimization guide.
c_fit resizes the image inside a maximum width and height without removing content. Use it when the complete image matters, such as editorial graphics, posters, or screenshots. Use a crop mode such as c_fill only when filling the frame matters more than preserving every edge.
f_auto reads the browser’s supported formats and can deliver formats such as AVIF or WebP. q_auto analyzes the image and selects an encoding quality that balances file size with visual quality. These transformations affect the delivered version, while the original asset remains available in Cloudinary.
Altelier renders a blurred decorative copy behind images that do not fill the showcase frame. The foreground uses the stored description. The decorative backdrop uses alt="" and aria-hidden="true".
View the full gallery component: https://github.com/musebe/react-cloudinary-alt-text/blob/main/src/components/asset-library.tsx
View the full gallery component:
View Portable React, HTML, Vue, and content management system examples: https://github.com/musebe/react-cloudinary-alt-text/blob/main/src/components/integration-code-panel.tsx
View Portable React, HTML, Vue, and content management system examples:
Test the result from upload to rendered HTML:
- Run npm run dev.
- Open the upload studio.
- Upload an image with a clear subject.
- Wait for Alt text saved in Cloudinary.
- Open the asset in Cloudinary Media Library.
- Confirm Description (alt) contains the caption.
- Confirm Tags contains autogenerated-alt.
- Inspect the rendered image and check its alt attribute.
- Review the asset under Accessibility library.
These checks cover three different layers. The Upload Widget confirms that Cloudinary accepted the file. The Media Library confirms that captioning and the success script stored metadata. Browser inspection confirms that React used the stored description in the final HTML.
If the image uploads without a description, confirm that the app uses the same preset you edited. Also confirm that Add AI captioning to your image and the On success script are saved.
The repository includes a Vercel Function for /api/* and a Vite production build. Add all variables from .env.example to the Vercel project before deploying. Do not add PORT, because Vercel manages the function runtime.
Vite reads VITE_ variables while building the frontend. Vercel Functions read server variables while handling API requests. Add both groups before triggering a production deployment, then redeploy whenever you change a frontend variable.
Run these checks before each deployment:
Follow the project’s Vercel deployment guide for environment scopes and production checks.
Follow the project’s Vercel deployment guide for environment scopes and production checks.
Alt text should not be an afterthought added to one page. Store the description with the image, make missing descriptions visible, and review AI output before publication.
Cloudinary can manage the upload, caption generation, metadata, responsive transformation, and optimized delivery. React only needs to render the right asset with the right description.
Explore the Altelier source repository, try the live accessibility studio, and compare this workflow with Cloudinary’s original article about automatically generating alt text for images.












