Sanity Image Text Block Plugin
Plugin to create image text combination block inside pages or any other document types. Responsive design with left or right image position, title, subtitle, description, button, background color, text color, and content alignment. Also supports button with custom background and text color.
By Multidots
Install command
npm install @multidots/sanity-plugin-image-text-blockSanity Image Text Block Plugin
By Multidots
A Sanity Studio v3+ plugin for creating image and text combination blocks with flexible positioning and styling.
š Features
- ā Image Positioning - Left or right side positioning
- ā Rich Content - Title, subtitle, and rich text descriptions
- ā Custom Buttons - Call-to-action buttons with custom styling
- ā Color Customization - Background, text, and button colors
- ā Content Alignment - Top or center alignment options
- ā Responsive Design - Works on all device sizes
- ā TypeScript Support - Fully typed
š¦ Installation
npm install @multidots/sanity-plugin-image-text-blockOr with yarn:
yarn add @multidots/sanity-plugin-image-text-blockš§ Add to Sanity Studio
1. Add Plugin to Sanity Config
import { defineConfig } from 'sanity'
import { ImageTextBlockPlugin } from '@multidots/sanity-plugin-image-text-block'
export default defineConfig({
// ... other config
plugins: [
ImageTextBlockPlugin(),
// ... other plugins
],
})2. Use in Schema
// schemas/page.ts
import { defineType, defineField } from 'sanity'
export default defineType({
name: 'page',
title: 'Page',
type: 'document',
fields: [
defineField({
name: "imageTextBlock",
type: "ImageTextBlockType",
}),
],
})āļø Frontend Usage Example (Next.js)
1. Add Field to Your Query
First, add the imageTextBlock field to your page or post query:
// lib/sanity/queries.ts
export const getPageQuery = defineQuery(`
*[_type == 'page' && slug.current == $slug][0]{
_id,
_type,
name,
slug,
imageTextBlock,
}
`);2. Create ImageText Wrapper Component
Create a wrapper component to handle the data transformation:
// components/ImageText.tsx
'use client'
import { ImageTextBlock } from "@multidots/sanity-plugin-image-text-block"
import { PortableTextBlock } from "sanity"
import type { SanityImageSource } from '@sanity/image-url/lib/types/types'
import { client } from '@/lib/sanity/client/client'
interface ImageTextProps {
title?: string
subTitle?: string
description?: PortableTextBlock[] | string | null
mainImage?: SanityImageSource
imagePosition?: "left" | "right"
backgroundColor?: {
hex: string
}
textColor?: {
hex: string
}
contentAlignment?: 'top' | 'center'
button?: {
text?: string
link?: string
openInNewTab?: boolean
buttonBackgroundColor?: {
hex: string
}
buttonTextColor?: {
hex: string
}
}
}
function portableTextToPlainText(value: PortableTextBlock[] | string | null | undefined): string {
if (!value) return ''
if (typeof value === 'string') return value
try {
return value
.map(block => {
if (block?._type !== 'block' || !('children' in block)) return ''
// @ts-expect-error: children exists on block content
return (block.children || []).map((child: any) => child.text).join('')
})
.join('\n')
} catch {
return ''
}
}
export const ImageText = ({ mainImage, title, subTitle, description, imagePosition, button, backgroundColor, textColor, contentAlignment }: ImageTextProps) => {
const descriptionText = portableTextToPlainText(description)
const normalizedButton = {
text: button?.text ?? '',
link: button?.link ?? '',
openInNewTab: button?.openInNewTab ?? false,
buttonBackgroundColor: button?.buttonBackgroundColor ?? { hex: '#667eea' },
buttonTextColor: button?.buttonTextColor ?? { hex: '#ffffff' },
}
return (
<ImageTextBlock
title={title || ''}
subTitle={subTitle || ''}
description={descriptionText}
mainImage={mainImage}
imagePosition={imagePosition}
button={normalizedButton}
backgroundColor={backgroundColor}
textColor={textColor}
contentAlignment={contentAlignment}
sanityClient={client}
/>
)
}3. Use in Your Page Component
// app/[slug]/page.tsx
import { ImageText } from "@/components/ImageText";
export default function Page({ page }) {
return (
<main>
{page?.imageTextBlock && (
<ImageText
title={page.imageTextBlock.title}
subTitle={page.imageTextBlock.subTitle}
description={page.imageTextBlock.description}
mainImage={page.imageTextBlock.mainImage}
imagePosition={page.imageTextBlock.imagePosition}
button={page.imageTextBlock.button}
backgroundColor={page.imageTextBlock.backgroundColor?.hex ? { hex: page.imageTextBlock.backgroundColor.hex } : undefined}
textColor={page.imageTextBlock.textColor?.hex ? { hex: page.imageTextBlock.textColor.hex } : undefined}
contentAlignment={page.imageTextBlock.contentAlignment}
/>
)}
</main>
)
}4. Environment Variables (.env.local)
Make sure to set up your environment variables:
NEXT_PUBLIC_SANITY_PROJECT_ID=your-project-id
NEXT_PUBLIC_SANITY_DATASET=productionšļø Plugin Settings and Screenshots
Available Fields in Sanity Studio
Content Fields
- Title - Main heading text
- Subtitle - Secondary heading text
- Description - Rich text content with formatting options
Image Fields
- Main Image - Image upload with alt text support
Styling Options
- Image Position - Choose left or right positioning
- Content Alignment - Top or center vertical alignment
- Background Color - Content area background color picker
- Text Color - Universal text color for all content
Button Configuration
- Button Text - Call-to-action button label
- Button Link - URL destination
- Open in New Tab - External link behavior
- Button Background Color - Custom button background
- Button Text Color - Custom button text color
Frontend Output Examples
Left Image Position:
[IMAGE] | Title
| Subtitle
| Description text here...
| [Button]Right Image Position:
Title | [IMAGE]
Subtitle |
Description... |
[Button] |š· Screenshots
Backend Settings: https://share.cleanshot.com/mfSSvb0FXGR7TmVspQSM
Frontend Output Image Left: https://share.cleanshot.com/jV7DnbbKvw3bmTsWXDNB
Frontend Output Image Right: https://share.cleanshot.com/Dqkrdrd2ywkLCkzzwFCS
Studio Interface
Frontend Output
Demo Video
https://share.cleanshot.com/w0xnp7mbGV72Y7K7tYvW
š Troubleshooting
Images not displaying
Problem: Images appear broken or don't load Solution: Ensure you're passing the Sanity client properly:
<ImageTextBlock
{...blockData}
sanityClient={sanityClient}
/>Button links not working
Problem: Button clicks don't navigate Solution: Ensure the link field includes protocol:
ā
https://example.com
ā
/internal-page
ā example.com (missing protocol)š¤ Exports
The plugin exports the following components and types:
Main Exports
// Plugin for Sanity Studio
export const ImageTextBlockPlugin: PluginOptions
// React component for frontend
export const ImageTextBlock: React.FC<ImageTextBlockProps>
// Default export (plugin)
export default ImageTextBlockPluginTypeScript Interfaces
interface ImageTextBlockProps {
title?: string
subTitle?: string
description?: any // Rich text from Sanity
mainImage?: SanityImageSource & {
alt?: string
}
imagePosition?: 'left' | 'right'
backgroundColor?: {
hex: string
}
textColor?: {
hex: string
}
contentAlignment?: 'top' | 'center'
button?: {
text: string
link: string
openInNewTab: boolean
buttonBackgroundColor?: {
hex: string
}
buttonTextColor?: {
hex: string
}
}
sanityClient?: any // Sanity client for image URL generation
}Usage Examples
// Import plugin for Sanity config
import { ImageTextBlockPlugin } from '@multidots/sanity-plugin-image-text-block'
// Import component for React/Next.js
import { ImageTextBlock } from '@multidots/sanity-plugin-image-text-block'
// Import default (same as plugin)
import ImageTextBlockPlugin from '@multidots/sanity-plugin-image-text-block'Develop & test
This plugin uses @sanity/plugin-kit for build & watch scripts.
Local development tips:
- npm run link-watch or npm run watch to develop against a local Studio
- Publish with npm publish (build runs on prepublishOnly)



