• About
  • Blog
  • Tools
  • Case Studies
  • Contact

ContentWrap simplifies your Sanity CMS workflow

© Copyright 2025 ContentWrap. All Rights Reserved.

Work
  • About
  • Blog
  • Tools
  • Case Studies
  • Contact
Legal
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  1. Blog
  2. how to implement text wrap around images in sanity CMS
Sanity Setup
Rich Content & Embeds
Custom Features
Sanity
May 15, 2025

How to Implement Text Wrap Around Images in Sanity CMS: A Step-by-Step Guide

Tamba Monrose
Founder of ContentWrap
how to implement text wrap around images in sanity cms

In this article

  1. Introduction
  2. Solution Overview
  3. The Challenge
  4. Step-by-Step Implementation
  5. Final Result
  6. Conclusion
  7. Additional Resources

Share this article

Introduction

One of the most frustrating limitations content editors face when working with Sanity CMS is the inability to wrap text naturally around images. While platforms like WordPress and Webflow offer this functionality out of the box, Sanity's structured content approach requires a custom solution to achieve this fundamental layout technique.

Text wrapping (or floating images) is essential for creating visually appealing, readable content that integrates images naturally with your text. Without it, content can feel rigid and disconnected, with images either stacked between paragraphs or taking up the full width of the content area.

In this tutorial, we'll solve this problem by building a custom wrapTextAroundImage component for Sanity that gives your content editors the power to float images to the left or right of text, control image width, and maintain a clean reading experience.

By the end of this tutorial, you'll be able to:

  • Create a custom schema component in Sanity Studio
  • Configure image positioning controls for content editors
  • Implement the frontend rendering component
  • Integrate the solution with your existing Portable Text setup
  • Offer your content team a more flexible, professional publishing experience

Prerequisites

  • A working Sanity CMS project
  • Basic knowledge of React and TypeScript
  • Familiarity with Sanity schemas and Portable Text
  • Next.js application setup for frontend rendering

Solution Overview

Our solution involves creating a custom Portable Text block type in Sanity that combines an image with associated content, along with controls for positioning and width. This component will:

  1. Allow content editors to select an image from their media library
  2. Provide options to float the image left or right
  3. Control the image width as a percentage of the container
  4. Add caption and alt text for accessibility
  5. Include a content field where editors can write text that will wrap around the image

Example component input:

Sanity dialog for setting properties of wrapped text around image

Example 1 component output:

(The following text and image come from A Tale of Cities.)

a tale of two cities
A Tale of Two Cities

"It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way--in short, the period was so far like the present period that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only."

Example 2 component output:

(The following text and image come from The White Darkness piece, written in The New Yorker Magazine in 2018. A user on StackOverflow asked how he could recreate this specific figure with Sanity CMS.)

Adams, Worsley, and Gow reached the South Pole in 2009, completing, in Gow’s words, “unfinished family business.”

"In 2012, Worsley launched a new expedition, to mark the centennial of the race between Amundsen and Scott to the South Pole. Gow and Adams, who had married and settled down with families, declined to go, and so Worsley drew recruits from the military. He and a partner, Lou Rudd, followed the trail of Amundsen and raced against another party, which took the route of Scott. Once more, Worsley proved an extraordinary commander—Rudd called him a “true inspiration”—and they won the nine-hundred-mile contest, which raised nearly three hundred thousand dollars for a Royal British Legion fund that assists wounded soldiers."

The implementation leverages Sanity's Portable Text for structured content handling while adding custom controls for the visual presentation. On the frontend, we'll use CSS floats with appropriate margins to create the text wrapping effect.

This approach is superior to alternatives like using fixed-position components or grid-based layouts because it:

  • Preserves the natural flow of content
  • Adapts responsively to different screen sizes
  • Maintains proper document structure for accessibility
  • Gives content editors direct control over presentation

The Challenge

Sanity CMS is built around the concept of structured content, which separates content from presentation. While this provides excellent flexibility and reusability, it can make certain common layout patterns challenging to implement.

By default, Sanity's Portable Text editor treats images as block-level elements that occupy the full width of their container. This creates a disconnected experience where text appears either above or below images, rather than flowing around them.

Content editors coming from traditional WYSIWYG editors expect the ability to position images within text. It's a fundamental layout capability that enhances readability and visual appeal. Without this option, content can feel clunky and overly structured.

The technical challenges we need to overcome include:

  1. Creating a custom block type that combines image and content fields
  2. Providing intuitive controls for image position and width
  3. Ensuring proper rendering on the frontend using CSS floats
  4. Maintaining proper text flow and whitespace around the image
  5. Ensuring the solution works responsively across device sizes

Step-by-Step Implementation

Let's break down the implementation process into manageable steps:

Step 1: Creating the Schema Definition

First, we need to define our custom wrapTextAroundImage schema in Sanity. This will form the foundation of our component.

TypeScriptschemas/objects/wrapTextAroundImage.ts
export const wrapTextAroundImage = {  name: 'wrapTextAroundImage',  title: 'Wrap Text Around Image',  type: 'object',  fields: [    {      name: 'image',      title: 'Image',      type: 'image',      options: {        hotspot: true,      },      fields: [        {          name: 'alt',          title: 'Alternative Text',          type: 'string',          description: 'Important for accessibility and SEO',        },        {          name: 'caption',          title: 'Caption',          type: 'string',          description: 'Optional caption to display below the image',        },      ],    },    {      name: 'position',      title: 'Position',      type: 'string',      options: {        list: [          { title: 'Left', value: 'left' },          { title: 'Right', value: 'right' },        ],        layout: 'radio',      },      initialValue: 'left',    },    {      name: 'width',      title: 'Width (%)',      type: 'number',      description: 'Width of the image as percentage of container',      validation: (Rule) => Rule.min(10).max(60),      initialValue: 30,    },    {      name: 'content',      title: 'Content',      type: 'array',      of: [{ type: 'block' }],      description: 'Text that will wrap around the image',    },  ],  preview: {    select: {      image: 'image',      position: 'position',      width: 'width',    },    prepare({ image, position, width }) {      return {        title: 'Text with Wrapped Image',        subtitle: `Position: ${position || 'left'}, Width: ${width || 30}%`,        media: image,      };    },  },};

This schema creates a complex object that includes:

  • An image field with support for hotspot positioning, alt text, and captions
  • A position field with radio button options for left or right alignment
  • A width field that accepts a percentage value with validation
  • A content field that uses Portable Text for rich text editing

The preview configuration ensures that the component is easily identifiable in the Sanity Studio interface, showing a thumbnail of the selected image and the positioning details.

Step 2: Integrating with Document Schemas

Next, we need to integrate our new component with existing document schemas so that it's available to content editors.

TypeScriptschemas/documents/post.ts
export const post = {  name: 'post',  title: 'Blog Post',  type: 'document',  fields: [    // ... existing fields like title, slug, etc.    {      name: 'content',      title: 'Content',      type: 'array',      of: [        { type: 'block' },        { type: 'image' },        // Add our new component to the available block types        { type: 'wrapTextAroundImage' }      ],    },  ],  // ... rest of schema};

Step 3: Building the Frontend Renderer

Now we need to create the frontend component that will render our custom block type. This is where the magic happens, transforming the structured content into a visually appealing layout.

Based on the code you provided, let's build out a complete implementation:

Reactcomponents/PortableText.tsx
import {  type PortableTextComponents,  PortableText as SanityPortableText,} from '@portabletext/react';import { PortableTextObject } from 'sanity';import { cn } from '@kit/ui/utils';import { SanityImage } from './SanityImage';export function PortableText({ value }) {  const components = {    types: {      wrapTextAroundImage: ({ value }) => {        const { image, position = 'left', width = 30, content } = value;        const floatClass = 'left'            ? 'float-left mr-6'            : 'float-right ml-6';        return (          <div className="clear-both">            <figure              className={cn(floatClass, 'relative mb-4')}              style={{ width: `${width}%` }}            >              <SanityImage image={image} />              {image.caption && (                <figcaption className="mt-2 text-center text-sm">                  {image.caption}                </figcaption>              )}            </figure>            <PortableText value={content} />          </div>        );      },      // ... other custom components    },    // ... mark components, list components, etc.  };  return (    <div className="prose">      <SanityPortableText components={components} value={value} />    </div>  );}

This component:

  1. Takes a Portable Text value and renders it using @portabletext/react
  2. Includes a custom renderer for our wrapTextAroundImage block type
  3. Extracts the image, position, width, and content from the block value
  4. Applies appropriate CSS classes for floating the image left or right
  5. Sets the width of the image as a percentage using inline styles
  6. Renders the caption if one was provided
  7. Recursively renders the content field using the same PortableText component

The CSS classes float-left and float-right are used to create the text wrapping effect, while the margins (mr-6 and ml-6) ensure there's adequate spacing between the image and the text.

Step 4: Testing and Fine-tuning

After implementing the component, it's essential to test it thoroughly in various scenarios. Let's check for:

  1. Responsive behavior: How does the component behave on different screen sizes?
  2. Content overflow: What happens with very short or very long content?
  3. Image sizing: How do different width percentages affect the layout?
  4. Accessibility: Is the content still accessible with screen readers?

Based on testing, we might need to add additional CSS to handle edge cases:

CSSstyles/components/portableText.css
/* Clear floats at the end of content sections to prevent layout issues */.portable-text-content::after {  content: "";  display: table;  clear: both;}/* Responsive adjustments for small screens */@media (max-width: 640px) {  .portable-text-content figure.float-left,  .portable-text-content figure.float-right {    float: none;    width: 100% !important;    margin-left: 0;    margin-right: 0;  }}

Final Result

After implementing all the steps, content editors can now:

  1. Insert the "Wrap Text Around Image" component in their content
  2. Upload or select an image from the media library
  3. Choose whether the image should float left or right
  4. Adjust the image width to balance with the surrounding text
  5. Add a caption and alt text for accessibility
  6. Write content that will naturally flow around the image

The final result is a more professional, magazine-like layout that improves readability and visual interest; Text wraps nicely around the image, providing a natural reading flow while integrating visual elements directly with the content.

Complete Application Structure

Conclusion

In this tutorial, we've successfully implemented a custom component in Sanity CMS that allows content editors to wrap text around images — a fundamental layout capability that's missing from Sanity's default toolkit.

By creating a specialized block type with intuitive controls for positioning and width, we've significantly enhanced the content editing experience. The solution provides:

  • A natural way to integrate images with text
  • Control over image positioning and sizing
  • Support for captions and alt text
  • Responsive behavior across different screen sizes

This implementation demonstrates how Sanity's flexibility allows you to extend its capabilities to match the expectations of content editors coming from traditional WYSIWYG editors, without sacrificing the benefits of structured content.

Next Steps

To build on this implementation, consider:

  • Adding more positioning options (center with text wrap, full-width)
  • Implementing margin controls for fine-tuning spacing
  • Creating variant components for special cases (like pull quotes)
  • Expanding the content editor's toolkit with similar custom components for other layout patterns

Additional Resources

  • Sanity Documentation on Custom Components
  • Next.js Image Component Documentation
  • CSS Floats and Positioning
  • GROQ Query Language for Content Selection

Related Articles

adding open in new tab option to links in sanity cms
Sanity Setup
Custom Features
Sanity
May 2025
How to Add 'Open in New Tab' Option for Links in Sanity CMS: A Step-by-Step Guide

Learn how to add the missing "Open in New Tab" option to Sanity CMS links. This step-by-step guide shows you how to customize link annotations and properly implement frontend rendering for better content management.

superscript and subscript decorator options showing in decorators section of Sanity block array field
Sanity Setup
Custom Features
Sanity
April 2025
How to Add Superscripts and Subscripts to Sanity CMS: A Step-by-Step Guide

In this tutorial, you will learn how to extend Sanity CMS with custom superscript and subscript decorators for block array fields.

how to solve timezone issues in sanity cms with rich-date-input
Sanity Setup
Studio UX
Sanity
May 2025
How to Solve Timezone Issues in Sanity CMS with rich-date-input: A Step-by-Step Guide

Eliminate timezone headaches in Sanity CMS with the rich-date-input plugin. Learn how to prevent publishing date confusion and ensure consistent datetime display for global teams with this simple yet powerful solution.