Inline Image


The InlineImage field represents an image input. This field supports drag and drop upload, or via clicking on the image to select media from the local filesystem.

Definition

Below is a simple example of how InlineImage could be implemented in in an Inline Form.

import ReactMarkdown from 'react-markdown'
import { useForm, usePlugin } from 'tinacms'
import { InlineForm, InlineImage } from 'react-tinacms-inline'

// Example 'Page' Component
export function Page(props) {
  const [data, form] = useForm(props.data)
  usePlugin(form)
  return (
    <InlineForm form={form}>
      <InlineImage
        name="hero_image"
        parse={media => media.id}
        uploadDir={() => '/public/images/'}
        alt="hero-image"
      />
    </InlineForm>
  )
}

There are two ways to use InlineImage, with and without children. If no children are passed, InlineImage will render a default img element. However, you may want more control over the image behavior, in which case you can pass children to InlineImage.

Options

KeyDescription
nameThe path to some value in the data being edited.
parseDefines how the actual front matter or data value gets populated. The name of the file gets passed as an argument, and one can set the path this image as defined by the uploadDir property.
uploadDirOptional Defines the upload directory for the image. All of the post data is passed in, fileRelativePath is most useful in defining the upload directory, but you can also statically define the upload directory.
previewSrcOptional Defines the path for the src attribute on the image preview. If using gatsby-image, the path to the childImageSharp.fluid.src needs to be provided. This defaults to the MediaStore's previewSrc method.
focusRingOptional Either an object to style the focus ring or a boolean to show/hide the focus ring. Defaults to true which displays the focus ring with default styles. For style options, offset (in pixels) sets the distance from the ring to the edge of the component, and borderRadius (in pixels) controls the rounded corners of the focus ring.
classNameOptional A string associated with a CSS class to extend the inline image container styled component.
altOptional A string to pass for the alt attribute of the img element when not using the Render Props pattern.
childrenOptional Children need to be passed through the Render Props pattern to access src.

Interface

interface InlineImageProps {
  name: string
  parse(media: Media): string
  uploadDir?(form: Form): string
  previewSrc?(
    src: string,
    fieldPath?: string,
    formValues?: any
  ): Promise<string> | string
  focusRing?: boolean | FocusRingProps
  className?: string
  alt?: string
  children?: ImageRenderChildren
}

interface FocusRingProps {
  offset?: number | { x: number; y: number }
  borderRadius?: number
}

interface ImageRenderChildrenProps {
  src?: string
}

type ImageRenderChildren = (props: ImageRenderChildrenProps) => React.ReactNode

The parse function handles how the path gets written in the source data when a new image is uploaded. parse is passed the media object for the newly uploaded image. A simple return value could look something like this:

parse: media => media.id

The path depends on where images live in your project structure. uploadDir sets where those new images should live:

uploadDir: () => `/example-dir`

The previewSrc provides a path for the image when inline editing is active (a.k.a when the CMS is enabled). When inline editing is not active (cms.enabled === false), the image will reference the path in the source data.

previewSrc is passed the current field value, the field path, and current form values. The return value should be the entire path to the image from the source data — e.g. /example-dir/image.jpg.

You can extend styles of the InlineImage container via styled-components or with a className.

Examples

Image Block

Below is an example of an Inline Image field used in an Inline Block in a Next.js project.

import { useCMS } from 'tinacms'
import { BlocksControls, InlineImage } from 'react-tinacms-inline'

export function Image({ data, index }) {
  const cms = useCMS()

  return (
    <div className="block">
      <BlocksControls
        index={index}
        focusRing={{ offset: { x: 0, y: 0 }, borderRadius: 0 }}
        insetControls
      >
        <InlineImage
          name="src"
          previewSrc={fieldValue => cms.media.previewSrc(`public${fieldValue}`)}
          parse={media => `/img/${media.filename}`}
          uploadDir={() => '/public/img/'}
          className="img--wrap"
          alt={data.alt}
          focusRing={false}
        />
      </BlocksControls>
    </div>
  )
}

The className will apply corresponding styles to the Inline Image container div. In this case, the focusRing is false because the

Render Props Pattern

Below is the same example, refactored to use the Render Props pattern:

import { useCMS } from 'tinacms'
import { BlocksControls, InlineImage } from 'react-tinacms-inline'

export function Image({ data, index }) {
  const cms = useCMS()

  return (
    <div className="block">
      <BlocksControls
        index={index}
        focusRing={{ offset: { x: 0, y: 0 }, borderRadius: 0 }}
        insetControls
      >
        <InlineImage
          name="src"
          previewSrc={fieldValue => cms.media.previewSrc(`public${fieldValue}`)}
          parse={media => `/img/${media.filename}`}
          uploadDir={() => '/public/img/'}
          className="img--wrap"
          focusRing={false}
        >
          {props => <img src={props.src} alt={data.alt} />}
        </InlineImage>
      </BlocksControls>
    </div>
  )
}

With this pattern, the src is passed as a prop to the render children. The value of src will either be the return value from the previewSrc function (when the CMS is enabled) or the value stored in the source data (when the CMS is disabled).

Using Gatsby Image

Below is an example of how you could pass children as a to InlineImage to work with Gatsby Image. Notice how children need to be passed via render props.

You'll notice that this component looks a bit different with its handling of src. Since with Gatsby Image, the path to the image is transformed, you'll need to provide a fallback src that references the transformed path from childImageSharp.

Also, the previewSrc function uses the third argument, formValues to access the entire form and retrieve the transformed, childImageSharp path.

Read more on proper image paths in Gatsby to get context on the parse & uploadDir configuration.

import { InlineForm, InlineImage } from 'react-tinacms-inline'
import { useRemarkForm } from 'gatsby-tinacms-remark'
import { usePlugin, useCMS } from 'tinacms'
import Img from 'gatsby-image'

// Using InlineImage with Gatsby Image
export function Hero({ data }) {
  const cms = useCMS()
  const [post, form] = useRemarkForm(data.markdownRemark)

  usePlugin(form)

  return (
    <InlineForm form={form}>
      <InlineImage
        name="rawFrontmatter.thumbnail"
        parse={media => (media.filename ? `./${filename}` : null)}
        uploadDir={blogPost => {
          const postPathParts = blogPost.initialValues.fileRelativePath.split(
            '/'
          )

          const postDirectory = postPathParts
            .splice(0, postPathParts.length - 1)
            .join('/')

          return postDirectory
        }}
        previewSrc={(fieldValue, fieldPath, formValues) =>
          formValues.frontmatter.thumbnail.childImageSharp.fluid.src
        }
      >
        {props => (
          <Img
            fluid={
              cms.enabled
                ? props.src
                : post.frontmatter.thumbnail.childImageSharp.fluid
            }
            alt="Gatsby can't find me"
            {...props}
          />
        )}
      </InlineImage>
    </InlineForm>
  )
}

Using next/image

Note that the same principles for Gatsby Image can be applied to the next/image component. This gives you the benefits of automatic image optimisation, but comes with some caveats. You can read more about how this works on the Next.js docs.

Below is an example of how you could pass children as a to InlineImage to work with next/image. Notice how children need to be passed via render props. You can read more about the next/image component on the Next.js API reference docs.

You can optionally pass specific widths to the image element, but the example below shows how you could use padding to create a container with an aspect ratio (in this case a square).

Don't forget to update your next.config.js if you're planning on passing in sources from external websites.

import { InlineForm, InlineImage } from 'react-tinacms-inline'
import { useGithubJsonForm } from "react-tinacms-github";
import { usePlugin, useCMS } from 'tinacms'
import Image from '@next/image'

// Using InlineImage with next/image
export function Hero({ file, formConfig }) { 
  const cms = useCMS()
  
  // Note for simplicity's sake, the below assumes you're passing file and formConfig props.
  const [data, form] = useGithubJsonForm(file, formConfig);

  usePlugin(form)

  return (
    <InlineForm form={form}>
      <InlineImage
        name="image"
        parse={(media: any) => {
          return `/${media.id}`;
        }}
        uploadDir={() => "/images"}
        alt="Some descriptive alt text here"
      >
        {(props) => (
          <div
            style={{
              height: 0,
              paddingBottom: "100%"
            }}
          >
            <Image
              src={props.src}
              alt={props.alt}
              layout="fill"
              objectFit="cover"
            />
          </div>
        )}
      </InlineImage>
    </InlineForm>
  )
}

Last Edited: September 24, 2020