← Back to all how-tos

Create a brick

Add an editable image

Add a visually editable image to your custom brick and place it beside the text.

Estimated time: 3 minRaw Markdown

What you'll build

In this how-to, you'll add a visually editable image to a TextImage brick using the React Bricks <Image> component.

You'll:

  • add an image prop to the component interface
  • import Image from the React Bricks package for your platform
  • wrap the brick in a container
  • create a two-column grid with text on the left and image on the right

At the end, editors will be able to upload and replace the image directly in the visual editor.

Start from the previous example

If you're starting from the brick used in the previous how-to, your TextImage should look like this:

import { Link, RichText, Text, types } from 'react-bricks/rsc'
 
interface TextImageProps {
  title: types.TextValue
  description: types.TextValue
}
 
const TextImage: types.Brick<TextImageProps> = ({ title, description }) => {
  return (
    <>
      <Text
        propName="title"
        value={title}
        placeholder="Type a title..."
        renderBlock={({ children }) => (
          <h2 className="text-3xl font-extrabold">{children}</h2>
        )}
      />
 
      <RichText
        propName="description"
        value={description}
        placeholder="Type a description..."
        renderBlock={({ children }) => (
          <p className="text-lg text-slate-600">{children}</p>
        )}
        allowedFeatures={[
          types.RichTextFeatures.Bold,
          types.RichTextFeatures.Link,
        ]}
        renderBold={({ children }) => (
          <b className="text-pink-500">{children}</b>
        )}
        renderLink={({ children, href, target, rel }) => (
          <Link
            href={href}
            target={target}
            rel={rel}
            className="text-sky-500 hover:text-sky-600 transition-colors"
          >
            {children}
          </Link>
        )}
      />
    </>
  )
}
 
TextImage.schema = {
  name: 'text-image',
  label: 'Text Image',
 
  getDefaultProps: () => ({
    title: 'Thick as a brick',
    description: 'Another brick in the wall',
  }),
}
 
export default TextImage

Now we'll add an image and place the content in two columns.

Add the image prop

First, import Image from react-bricks/rsc if you're using Next.js with the App Router, or from react-bricks/astro if you're using Astro, and add a new image prop to the interface:

import { Image, Link, RichText, Text, types } from 'react-bricks/rsc'
 
interface TextImageProps {
  title: types.TextValue
  description: types.TextValue
  image: types.IImageSource
}

Then read image from the component props:

const TextImage: types.Brick<TextImageProps> = ({
  title,
  description,
  image,
}) => {

types.IImageSource is the type React Bricks uses for editable image fields.

Add the container and grid layout

Now wrap the brick in a container and use a two-column grid:

return (
  <div className="container">
    <div className="grid lg:grid-cols-2 gap-12">
      <div>{/* current text content goes here */}</div>
      <div>{/* image goes here */}</div>
    </div>
  </div>
)

This gives you:

  • text on the left
  • image on the right on large screens
  • a single-column layout on smaller screens

Add the image component

Inside the grid, create one <div> for the text and one for the image:

<div className="container">
  <div className="grid lg:grid-cols-2 gap-12">
    <div>
      <Text
        propName="title"
        value={title}
        placeholder="Type a title..."
        renderBlock={({ children }) => (
          <h2 className="text-3xl font-extrabold">{children}</h2>
        )}
      />
 
      <RichText
        propName="description"
        value={description}
        placeholder="Type a description..."
        renderBlock={({ children }) => (
          <p className="text-lg text-slate-600">{children}</p>
        )}
        allowedFeatures={[
          types.RichTextFeatures.Bold,
          types.RichTextFeatures.Link,
        ]}
        renderBold={({ children }) => (
          <b className="text-pink-500">{children}</b>
        )}
        renderLink={({ children, href, target, rel }) => (
          <Link
            href={href}
            target={target}
            rel={rel}
            className="text-sky-500 hover:text-sky-600 transition-colors"
          >
            {children}
          </Link>
        )}
      />
    </div>
 
    <div>
      <Image
        propName="image"
        source={image}
        alt="Image"
        maxWidth={800}
        aspectRatio={16 / 9}
      />
    </div>
  </div>
</div>

Here:

  • propName="image" binds the editor field to the image prop
  • source={image} passes the current image value
  • alt="Image" is the fallback alt text
  • maxWidth={800} helps React Bricks optimize the generated images
  • aspectRatio={16 / 9} keeps the image shape consistent

Why maxWidth matters

React Bricks uses the maxWidth value to generate optimized responsive images.

That means it can create image sizes appropriate for the actual space used by the brick, instead of serving a larger image than necessary.

Final code

Your full TextImage.tsx file should now look like this:

import { Image, Link, RichText, Text, types } from 'react-bricks/rsc'
 
interface TextImageProps {
  title: types.TextValue
  description: types.TextValue
  image: types.IImageSource
}
 
const TextImage: types.Brick<TextImageProps> = ({
  title,
  description,
  image,
}) => {
  return (
    <div className="container">
      <div className="grid lg:grid-cols-2 gap-12">
        <div>
          <Text
            propName="title"
            value={title}
            placeholder="Type a title..."
            renderBlock={({ children }) => (
              <h2 className="text-3xl font-extrabold">{children}</h2>
            )}
          />
 
          <RichText
            propName="description"
            value={description}
            placeholder="Type a description..."
            renderBlock={({ children }) => (
              <p className="text-lg text-slate-600">{children}</p>
            )}
            allowedFeatures={[
              types.RichTextFeatures.Bold,
              types.RichTextFeatures.Link,
            ]}
            renderBold={({ children }) => (
              <b className="text-pink-500">{children}</b>
            )}
            renderLink={({ children, href, target, rel }) => (
              <Link
                href={href}
                target={target}
                rel={rel}
                className="text-sky-500 hover:text-sky-600 transition-colors"
              >
                {children}
              </Link>
            )}
          />
        </div>
 
        <div>
          <Image
            propName="image"
            source={image}
            alt="Image"
            maxWidth={800}
            aspectRatio={16 / 9}
          />
        </div>
      </div>
    </div>
  )
}
 
TextImage.schema = {
  name: 'text-image',
  label: 'Text Image',
 
  getDefaultProps: () => ({
    title: 'Thick as a brick',
    description: 'Another brick in the wall',
  }),
}
 
export default TextImage

Try it in the editor

Go back to the visual editor and add or edit the Text Image brick.

Now you can:

  • click the image placeholder to upload an image
  • replace the image directly in the editor
  • keep editing the title and description inline
  • see the layout switch to two columns on larger screens

If you don't set an alt text in the editor, the fallback "Image" value is used.

What's next

Your brick now combines editable text, rich text, and an editable image.

The next step is usually to add sidebar controls for layout and appearance, such as toggles, select options, or alignment settings.