{"id":28371,"date":"2022-03-23T22:23:33","date_gmt":"2022-03-23T22:23:33","guid":{"rendered":"http:\/\/Generating-Alt-Text-for-Images-in-Redwood"},"modified":"2025-03-02T06:36:40","modified_gmt":"2025-03-02T14:36:40","slug":"generating-alt-text-for-images-in-redwood","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/","title":{"rendered":"Generating Alt Text for Images"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Every image should have some descriptive alt text so that our content stays as accessible as we can make it. That\u2019s why it\u2019s a good idea to start with accessibility in mind every time you write code. So we\u2019ll create a way to add automatically add this text to our images based on the data we currently have.<\/p>\n<p>In this post, we\u2019ll take a look at how we can generate default alt text based on descriptions stored in a database. This will ensure that even if we don\u2019t manually add alt text, there will be something to let users know what an image is.<\/p>\n<h2>Starting up the project<\/h2>\n<p>We\u2019ll be working with the Redwood framework so let\u2019s generate a new project. Open a terminal and run the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ yarn create redwood-app --typescript alt-text-generator\n<\/code><\/span><\/pre>\n<p>This will make some new files and folders for you and it gives you a completely functional full-stack TypeScript app. We\u2019ll pay the most attention to the <code>web<\/code> and <code>api<\/code> folders that hold the front-end and back-end respectively. Let\u2019s start by connecting to a database.<\/p>\n<h2>Setting up the database<\/h2>\n<p>We\u2019ll be working with a Postgres instance. If you don\u2019t have a local version of it, you can download <a href=\"https:\/\/www.postgresql.org\/download\/\">Postgres here<\/a>. You\u2019ll need to know your username and password in order to connect it to the application.<\/p>\n<p>The first thing we\u2019ll do is open the <code>.env<\/code> file and update the <code>DATABASE_URL<\/code> value. This is the connection string to the database. Yours might look something like this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">DATABASE_URL=postgres:<span class=\"hljs-comment\">\/\/admin:password@localhost:5432\/alt_text<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now that we have our database connection string defined, let\u2019s go to <code>api &gt; db<\/code> and open <code>schema.prisma<\/code>. This is where we actually connect to the database with the string we just defined and define the database schema. First, we\u2019ll need to update the <code>provider<\/code> value from <code>sqlite<\/code> to <code>postgresql<\/code>. This tells the app the kind of database it\u2019s working with.<\/p>\n<p>Next, we\u2019ll delete the <code>UserExample<\/code> model and replace it with our own. This app is going to let us upload images to the Cloudinary so that we can get a URL along with a name and description so our model needs to account for that.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">model Image {\n  id            Int     @id @<span class=\"hljs-keyword\">default<\/span>(autoincrement())\n  name          <span class=\"hljs-built_in\">String<\/span>  @unique\n  description   <span class=\"hljs-built_in\">String<\/span>\n  url           <span class=\"hljs-built_in\">String<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is the only model we\u2019ll need for the app right now, but you can always come back here and add new models as your app grows and new business needs come up. Now we need to migrate this to our database to set up the schema. To do that, run the following command in your terminal:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ yarn redwood prisma migrate dev\n<\/code><\/span><\/pre>\n<p>You\u2019ll be prompted to name the migration and then it\u2019ll update the database with the schema and any seed data you define. With the database migration running successfully, we can move on to setting up the GraphQL back-end of the app.<\/p>\n<h2>GraphQL server<\/h2>\n<p>Our back-end will use GraphQL to handle all of the requests our front-end needs. One of the cool things about Redwood is that there\u2019s a command to do almost everything. Since we have our database schema in place, we can use the following command to generate the types, queries, and mutations to handle the CRUD for the app.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ yarn redwood generate sdl --crud image\n<\/code><\/span><\/pre>\n<p>If you take a look in <code>api &gt; src &gt; services<\/code>, you\u2019ll see a new <code>images<\/code> folder. This has a few different files, but the main one is <code>images.ts<\/code>. Open this and you\u2019ll see all of the resolvers to handle CRUD requests. Now take a look in <code>api &gt; src &gt; graphql<\/code> and you\u2019ll see the <code>images.sdl.ts.<\/code> file. This has all of the types to support the resolvers we just saw.<\/p>\n<p>All of this was generated by that one command and this is the entire back-end. Everything is in place and ready to go for the front-end!<\/p>\n<h2>Making the user interface<\/h2>\n<p>Let\u2019s start by adding a package we\u2019ll need. In your terminal, go to the <code>web<\/code> directory and run the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ yarn add react-cloudinary-upload-widget\n<\/code><\/span><\/pre>\n<p>This will give us the package we need to upload images directly to Cloudinary and get the URL for the image in the response. We\u2019ll use that URL as the source for the image when we get ready to render them on the page with the alt text.<\/p>\n<p>Now we\u2019ll run a command to generate a page for users to upload and view their images. In your terminal, go back to the root directory and run this command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ yarn redwood generate page image\n<\/code><\/span><\/pre>\n<p>Now we have the component we\u2019ll need to start making this alt text generator. Go to <code>web &gt; src &gt; pages &gt; ImagePage<\/code> and open the <code>ImagePage.tsx<\/code>. This is where we\u2019ll start building the uploader functionality. You can go ahead and delete everything out of this component and we\u2019ll start fresh with the imports.<\/p>\n<h3>Adding image uploader<\/h3>\n<p>Let\u2019s add the imports we\u2019ll need to get this component working. At the top of the <code>ImagePage.tsx<\/code> file, add these import statements.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { MetaTags, useMutation, useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { WidgetLoader, Widget } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-cloudinary-upload-widget'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now let\u2019s start filling in the component by adding the Cloudinary upload button.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> ImagePage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> uploadFn = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> { }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">MetaTags<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Image\"<\/span> <span class=\"hljs-attr\">description<\/span>=<span class=\"hljs-string\">\"Image page\"<\/span> \/&gt;<\/span>\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>ImagePage<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">WidgetLoader<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Widget<\/span>\n        <span class=\"hljs-attr\">sources<\/span>=<span class=\"hljs-string\">{&#91;<\/span>'<span class=\"hljs-attr\">local<\/span>', '<span class=\"hljs-attr\">camera<\/span>']}\n        <span class=\"hljs-attr\">cloudName<\/span>=<span class=\"hljs-string\">{<\/span>`${<span class=\"hljs-attr\">cloudName<\/span>}`}\n        <span class=\"hljs-attr\">uploadPreset<\/span>=<span class=\"hljs-string\">{<\/span>`${<span class=\"hljs-attr\">uploadPresetName<\/span>}`}\n        <span class=\"hljs-attr\">buttonText<\/span>=<span class=\"hljs-string\">{<\/span>'<span class=\"hljs-attr\">Open<\/span>'}\n        <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span>\n          <span class=\"hljs-attr\">color:<\/span> '<span class=\"hljs-attr\">white<\/span>',\n          <span class=\"hljs-attr\">border:<\/span> '<span class=\"hljs-attr\">none<\/span>',\n          <span class=\"hljs-attr\">width:<\/span> '<span class=\"hljs-attr\">120px<\/span>',\n          <span class=\"hljs-attr\">backgroundColor:<\/span> '<span class=\"hljs-attr\">green<\/span>',\n          <span class=\"hljs-attr\">borderRadius:<\/span> '<span class=\"hljs-attr\">4px<\/span>',\n          <span class=\"hljs-attr\">height:<\/span> '<span class=\"hljs-attr\">25px<\/span>',\n        }}\n        <span class=\"hljs-attr\">folder<\/span>=<span class=\"hljs-string\">{<\/span>'<span class=\"hljs-attr\">alt_text_imgs<\/span>'}\n        <span class=\"hljs-attr\">onSuccess<\/span>=<span class=\"hljs-string\">{uploadFn}<\/span>\n      \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  )\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The main piece to note here is the <code>&lt;Widget&gt;<\/code>. It has several props that we\u2019ll pay attention to. The <code>cloudName<\/code>, <code>uploadPresetName<\/code>, and <code>folder<\/code> all come from your Cloudinary account. If you aren\u2019t sure what your <code>cloudName<\/code> and <code>uploadPresetName<\/code> are, you can find them in your <a href=\"https:\/\/cloudinary.com\/console\/settings\/upload\">dashboard<\/a>. The <code>folder<\/code> prop can be anything. If the folder doesn\u2019t exist, then it\u2019ll be automatically created.<\/p>\n<p>The other thing to note is that the <code>uploadFn<\/code> is just a placeholder at this point, but we\u2019ll add the code for it in just a bit. For now, go ahead and run the app with the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ yarn redwood dev\n<\/code><\/span><\/pre>\n<p>This will start up the front-end and back-end of the app and it should open your browser to <code>localhost:8910<\/code>. From there, you\u2019ll need to navigate to <code>localhost:8910\/image<\/code>. This is what you should see so far.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1644349654\/e-603fc55d218a650069f5228b\/iwnrf5ea1r7a1b5jlcvc.png\" alt=\"the image upload button\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1542\" height=\"904\"\/><\/p>\n<p>It\u2019s just the upload button, but if you test it out it does let you upload images to Cloudinary. Now we can work on how the alt text will be generated.<\/p>\n<h3>The alt text generation<\/h3>\n<p>One of the funny parts about software is that we get all of the data from user input. So to generate the alt text for each image, we\u2019re going to get some input from our users. Let\u2019s add a few fields to this component and a few states.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> ImagePage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> &#91;name, setName] = useState&lt;string&gt;(<span class=\"hljs-string\">\"\"<\/span>)\n  <span class=\"hljs-keyword\">const<\/span> &#91;description, setDescription] = useState&lt;string&gt;(<span class=\"hljs-string\">\"\"<\/span>)\n\n  <span class=\"hljs-keyword\">const<\/span> uploadFn = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> { }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">MetaTags<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Image\"<\/span> <span class=\"hljs-attr\">description<\/span>=<span class=\"hljs-string\">\"Image page\"<\/span> \/&gt;<\/span>\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>ImagePage<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">WidgetLoader<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span> <span class=\"hljs-attr\">htmlFor<\/span>=<span class=\"hljs-string\">\"name\"<\/span>&gt;<\/span>Name of the image:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"name\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span> <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{e<\/span> =&gt;<\/span> setName(e.currentTarget.value)} \/&gt;\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span> <span class=\"hljs-attr\">htmlFor<\/span>=<span class=\"hljs-string\">\"description\"<\/span>&gt;<\/span>Description of the image:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"description\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span> <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{e<\/span> =&gt;<\/span> setDescription(e.currentTarget.value)} \/&gt;\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Widget<\/span>\n        <span class=\"hljs-attr\">sources<\/span>=<span class=\"hljs-string\">{&#91;<\/span>'<span class=\"hljs-attr\">local<\/span>', '<span class=\"hljs-attr\">camera<\/span>']}\n        <span class=\"hljs-attr\">cloudName<\/span>=<span class=\"hljs-string\">{<\/span>`${<span class=\"hljs-attr\">cloudName<\/span>}`}\n        <span class=\"hljs-attr\">uploadPreset<\/span>=<span class=\"hljs-string\">{<\/span>`${<span class=\"hljs-attr\">uploadPresetName<\/span>}`}\n        <span class=\"hljs-attr\">buttonText<\/span>=<span class=\"hljs-string\">{<\/span>'<span class=\"hljs-attr\">Open<\/span>'}\n        <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span>\n          <span class=\"hljs-attr\">color:<\/span> '<span class=\"hljs-attr\">white<\/span>',\n          <span class=\"hljs-attr\">border:<\/span> '<span class=\"hljs-attr\">none<\/span>',\n          <span class=\"hljs-attr\">width:<\/span> '<span class=\"hljs-attr\">120px<\/span>',\n          <span class=\"hljs-attr\">backgroundColor:<\/span> '<span class=\"hljs-attr\">green<\/span>',\n          <span class=\"hljs-attr\">borderRadius:<\/span> '<span class=\"hljs-attr\">4px<\/span>',\n          <span class=\"hljs-attr\">height:<\/span> '<span class=\"hljs-attr\">25px<\/span>',\n        }}\n        <span class=\"hljs-attr\">folder<\/span>=<span class=\"hljs-string\">{<\/span>'<span class=\"hljs-attr\">alt_text_imgs<\/span>'}\n        <span class=\"hljs-attr\">onSuccess<\/span>=<span class=\"hljs-string\">{uploadFn}<\/span>\n      \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  )\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here, we\u2019ve added two states to store a name and a description for the image that we\u2019ll use to make the alt text for each image we upload. You can see the input fields for these values right above the <code>&lt;Widget&gt;<\/code> element. These update the states whenever a user types something in the field.<\/p>\n<p>Make sure the app is still running and if it\u2019s not, run <code>yarn redwood dev<\/code> in your terminal. Now you should see something like this in the browser.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1644349672\/e-603fc55d218a650069f5228b\/lmobpnyco0mcqakkenvj.png\" alt=\"the image uploader and labels\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1874\" height=\"1092\"\/><\/p>\n<p>Now we\u2019re ready to connect the upload functionality to our back-end. Whenever we upload an image, we want a new record created in the database that is associated with the user input we receive.<\/p>\n<h3>Implementing the upload\/create record functionality<\/h3>\n<p>We\u2019ll start by adding a GraphQL mutation right below all of the import statements.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> CREATE_IMAGE_MUTATION = gql`\n  mutation CreateImageMutation($input: CreateImageInput!) {\n    createImage(input: $input) {\n      id\n    }\n  }\n`\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This calls one of the resolvers on the back-end with the inputs the user enters. Now we\u2019ll create the function we need in order to use this mutation. Inside the component, right above the states, add the following line.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> ImagePage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-comment\">\/\/ add the line below to the existing code<\/span>\n  <span class=\"hljs-keyword\">const<\/span> &#91;createImage] = useMutation(CREATE_IMAGE_MUTATION)\n  <span class=\"hljs-keyword\">const<\/span> &#91;name, setName] = useState&lt;string&gt;(<span class=\"hljs-string\">\"\"<\/span>)\n  <span class=\"hljs-keyword\">const<\/span> &#91;description, setDescription] = useState&lt;string&gt;(<span class=\"hljs-string\">\"\"<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next, we can use the <code>createImage<\/code> method to help finish off the <code>uploadFn<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> uploadFn = <span class=\"hljs-function\">(<span class=\"hljs-params\">results: CloudinaryResult<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> imageInfo = results.info\n\n  <span class=\"hljs-keyword\">const<\/span> input = {\n      <span class=\"hljs-attr\">name<\/span>: name || imageInfo.original_filename,\n      <span class=\"hljs-attr\">description<\/span>: description,\n      <span class=\"hljs-attr\">url<\/span>: imageInfo.url\n    }\n  createImage({ <span class=\"hljs-attr\">variables<\/span>: { input } })\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Before we explain the new code, take a look at the <code>CloudinaryResult<\/code> type on the <code>results<\/code> argument. We\u2019re just taking advantage of having a TypeScript project and including a type definition for parts of the result we get from the Cloudinary upload result.<\/p>\n<p>We need to add this definition right below the GraphQL mutation.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">interface<\/span> <span class=\"hljs-selector-tag\">CloudinaryResult<\/span> {\n  <span class=\"hljs-attribute\">info<\/span>: {\n    original_filename: string\n    url: string\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This includes some of the data we\u2019ll take from the upload response that will be used for the alt text. Now let\u2019s look back at the <code>uploadFn<\/code>. This is taking a combination of the state values and values from the upload response to create the <code>input<\/code> for the <code>createImage<\/code> mutation. Then we call that function and the record gets added to the database.<\/p>\n<p>Go ahead and upload a few images with the inputs filled in so we\u2019ll have some data to play with in a second. We only have one more piece of functionality before this app is finished!<\/p>\n<h3>Displaying the images with the alt text<\/h3>\n<p>We\u2019ll need to add a GraphQL query to get the images we\u2019ve uploaded. So right below the existing mutation, add the following code.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> GET_IMAGES = gql<span class=\"hljs-string\">`\n  query {\n    images {\n      name\n      url\n      description\n    }\n  }\n`<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then we\u2019ll create the method we can call to get the image data. This will go inside the component, just above the mutation method.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> { data, loading } = useQuery(GET_IMAGES)\n<span class=\"hljs-keyword\">const<\/span> &#91;createImage] = useMutation(CREATE_IMAGE_MUTATION)\n<span class=\"hljs-keyword\">const<\/span> &#91;name, setName] = useState&lt;string&gt;(<span class=\"hljs-string\">\"\"<\/span>)\n<span class=\"hljs-keyword\">const<\/span> &#91;description, setDescription] = useState&lt;string&gt;(<span class=\"hljs-string\">\"\"<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019re getting two values from this query, the data and the loading state of that data. The reason we get the loading state of the data is to render an element to tell the user the data hasn\u2019t loaded yet. Once it <em>is<\/em> loaded, then we can render the images.<\/p>\n<p>Right below the <code>uploadFn<\/code> we\u2019re going to add a check to determine if we should show the loading message.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">if<\/span> (loading) {\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>That\u2019s all for that! Now whenever the data is loading, the app won\u2019t just crash on users. This is a good practice in any apps that you work on. With this in place, we can add the last bit to show the images. Add this code right below the <code>&lt;Widget&gt;<\/code> element.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">display:<\/span> '<span class=\"hljs-attr\">block<\/span>' }}&gt;<\/span>\n  {data?.images &amp;&amp;\n    data?.images.map(image =&gt; (\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n        <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{image.name}<\/span>\n        <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">padding:<\/span> '<span class=\"hljs-attr\">24px<\/span>', <span class=\"hljs-attr\">height:<\/span> '<span class=\"hljs-attr\">100px<\/span>', <span class=\"hljs-attr\">width:<\/span> '<span class=\"hljs-attr\">100px<\/span>' }}\n        <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{<\/span>`${<span class=\"hljs-attr\">image.url<\/span>}`}\n        <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{<\/span>`${<span class=\"hljs-attr\">image.name<\/span>} <span class=\"hljs-attr\">-<\/span> ${<span class=\"hljs-attr\">image.description<\/span>}`}\n      \/&gt;<\/span>\n    ))\n  }\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will bring all of the images you\u2019ve uploaded with the alt text already in place. If you run your app with <code>yarn redwood dev<\/code>, you should see something similar to this but with your own images.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1644349700\/e-603fc55d218a650069f5228b\/jqtkmjzdjboqxu819rky.png\" alt=\"the image uploader and list of images\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1682\" height=\"1096\"\/><\/p>\n<p>If you want to see what the alt text looks like, we can force a broken link in the <code>src<\/code> prop. Just change it a little like this: <code>src={<\/code>q${image.url}<code>}<\/code><\/p>\n<p>When you refresh the page, you should see something similar to this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1644349725\/e-603fc55d218a650069f5228b\/w0egp6fhcrfpop9srvx7.png\" alt=\"the image uploader and list of images with alt text\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1692\" height=\"1070\"\/><\/p>\n<p>We\u2019re finally finished! Now you can upload images and know that you always have alt text available so you don\u2019t have to think of a description on the fly.<\/p>\n<h2>Finished code<\/h2>\n<p>You can check out the full project in <a href=\"https:\/\/github.com\/flippedcoder\/media-projects\">the <code>alt-text-generator<\/code> folder of this repo<\/a>. Or you can check out the front-end in <a href=\"https:\/\/codesandbox.io\/s\/great-dewdney-lorjj\">this Code Sandbox<\/a>.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/great-dewdney-lorjj?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"great-dewdney-lorjj\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Conclusion<\/h2>\n<p>This is one of the ways to generate alt text for images in a way that might give you more accurate descriptions of an image than a machine learning model. Although you can use those too and see what kinds of funny results you get.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28372,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,246,371],"class_list":["post-28371","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-react","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Generating Alt Text for Images<\/title>\n<meta name=\"description\" content=\"Making images more accessible can start with ensuring alt text is available on everything. That&#039;s why we&#039;re going to make a quick app to automatically add alt text to images.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Generating Alt Text for Images\" \/>\n<meta property=\"og:description\" content=\"Making images more accessible can start with ensuring alt text is available on everything. That&#039;s why we&#039;re going to make a quick app to automatically add alt text to images.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:23:33+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-03-02T14:36:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268-jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"3888\" \/>\n\t<meta property=\"og:image:height\" content=\"2592\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Generating Alt Text for Images\",\"datePublished\":\"2022-03-23T22:23:33+00:00\",\"dateModified\":\"2025-03-02T14:36:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/\"},\"wordCount\":5,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"React\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/\",\"name\":\"Generating Alt Text for Images\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:23:33+00:00\",\"dateModified\":\"2025-03-02T14:36:40+00:00\",\"description\":\"Making images more accessible can start with ensuring alt text is available on everything. That's why we're going to make a quick app to automatically add alt text to images.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA\",\"width\":3888,\"height\":2592},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Generating Alt Text for Images\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Generating Alt Text for Images","description":"Making images more accessible can start with ensuring alt text is available on everything. That's why we're going to make a quick app to automatically add alt text to images.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/","og_locale":"en_US","og_type":"article","og_title":"Generating Alt Text for Images","og_description":"Making images more accessible can start with ensuring alt text is available on everything. That's why we're going to make a quick app to automatically add alt text to images.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:23:33+00:00","article_modified_time":"2025-03-02T14:36:40+00:00","og_image":[{"width":3888,"height":2592,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268-jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/"},"author":{"name":"","@id":""},"headline":"Generating Alt Text for Images","datePublished":"2022-03-23T22:23:33+00:00","dateModified":"2025-03-02T14:36:40+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/"},"wordCount":5,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA","keywords":["Guest Post","Image","React","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/","name":"Generating Alt Text for Images","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA","datePublished":"2022-03-23T22:23:33+00:00","dateModified":"2025-03-02T14:36:40+00:00","description":"Making images more accessible can start with ensuring alt text is available on everything. That's why we're going to make a quick app to automatically add alt text to images.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA","width":3888,"height":2592},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Generating Alt Text for Images"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"parsely":{"version":"1.1.0","canonical_url":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/","smart_links":{"inbound":0,"outbound":0},"traffic_boost_suggestions_count":0,"meta":{"@context":"https:\/\/schema.org","@type":"NewsArticle","headline":"Generating Alt Text for Images","url":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/","mainEntityOfPage":{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA&w=150&h=150&crop=1","image":{"@type":"ImageObject","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA"},"articleSection":"Uncategorized","author":[],"creator":[],"publisher":{"@type":"Organization","name":"Cloudinary Blog","logo":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA"},"keywords":["guest post","image","react","under review"],"dateCreated":"2022-03-23T22:23:33Z","datePublished":"2022-03-23T22:23:33Z","dateModified":"2025-03-02T14:36:40Z"},"rendered":"<meta name=\"parsely-title\" content=\"Generating Alt Text for Images\" \/>\n<meta name=\"parsely-link\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/generating-alt-text-for-images-in-redwood\/\" \/>\n<meta name=\"parsely-type\" content=\"post\" \/>\n<meta name=\"parsely-image-url\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA&w=150&amp;h=150&amp;crop=1\" \/>\n<meta name=\"parsely-pub-date\" content=\"2022-03-23T22:23:33Z\" \/>\n<meta name=\"parsely-section\" content=\"Uncategorized\" \/>\n<meta name=\"parsely-tags\" content=\"guest post,image,react,under review\" \/>","tracker_url":"https:\/\/cdn.parsely.com\/keys\/cloudinary.com\/p.js"},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924662\/Web_Assets\/blog\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268\/23da299678906e32962e9a15a56f1d1e36346db6-3888x2592-1_2837288268.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28371","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28371"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28371\/revisions"}],"predecessor-version":[{"id":37063,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28371\/revisions\/37063"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28372"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28371"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28371"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28371"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}