Have you ever needed to display scientific formulas like H2O or E=mc2 in your Sanity CMS content? Or perhaps you need to add trademark symbols, footnote references, or mathematical expressions to your website? While Sanity CMS offers powerful content management capabilities, it doesn't include superscript and subscript text decorators out of the box. This limitation can be frustrating when working with technical, scientific, or academic content.
In this comprehensive tutorial, I'll walk you through the process of extending Sanity's block content with custom superscript and subscript decorators. These text formatting options are essential for various content types, from scientific articles and legal disclaimers to academic references and product descriptions.
By the end of this tutorial, you'll be able to:
Before we dive in, make sure you have:
Sanity CMS uses a system called Portable Text (formerly Block Content) for rich text editing. This format allows for structured content with marks and decorators that can be serialized to different output formats. By default, Sanity includes basic text decorators like bold, italic, underline, and code, but it's designed to be extensible.
Our solution involves:
This approach maintains the structured nature of Portable Text while adding the specific formatting options we need. The benefit of this method is that it's non-destructive and follows Sanity's recommended patterns for customization.
Let's get started with the implementation!
Before adding custom decorators, it's important to understand how Sanity's block content works. In Sanity, rich text is stored as structured data using the Portable Text specification. Text decorators (also called "marks") are applied to spans of text within blocks.
The default block content in Sanity includes decorators like:
// Default decorators in Sanitydecorators: [ { title: 'Strong', value: 'strong' }, { title: 'Emphasis', value: 'em' }, { title: 'Code', value: 'code' }, { title: 'Underline', value: 'underline' }, { title: 'Strike', value: 'strike-through' },]
When a decorator is applied to text, it gets stored in the content's JSON structure like this:
{ "_type": "block", "children": [ { "_type": "span", "text": "This is ", "marks": [] }, { "_type": "span", "text": "bold", "marks": ["strong"] }, { "_type": "span", "text": " text", "marks": [] } ]}
Our goal is to add superscript
and subscript
as new decorator options, which will be stored similarly in the content structure.
To add custom decorators, we need to modify the schema file that defines your block content. This is typically found in your Sanity project directory structure.
schemas
directoryThe exact location might vary depending on your project structure, but it's commonly found in paths like:
schemas/blockContent.js
schemas/objects/blockContent.js
schemas/schema.js
(if you have a simpler project)If your project has multiple block content definitions, you'll need to modify each one where you want superscript and subscript to be available.
Now that you've found your schema file, let's add the superscript and subscript decorators:
// Before: Default block content schemaexport default { title: 'Block Content', name: 'blockContent', type: 'array', of: [ { title: 'Block', type: 'block', styles: [ { title: 'Normal', value: 'normal' }, { title: 'H1', value: 'h1' }, { title: 'H2', value: 'h2' }, { title: 'H3', value: 'h3' }, { title: 'Quote', value: 'blockquote' }, ], lists: [{title: 'Bullet', value: 'bullet' }, {title: 'Number', value: 'number' }], marks: { decorators: [ { title: 'Strong', value: 'strong' }, { title: 'Emphasis', value: 'em' }, { title: 'Code', value: 'code' }, { title: 'Underline', value: 'underline' }, { title: 'Strike', value: 'strike-through' }, ], annotations: [ // Annotations like links would be here ] } } ]}
Now, let's modify it to add our new decorators:
// After: Modified block content schema with superscript and subscriptexport default { title: 'Block Content', name: 'blockContent', type: 'array', of: [ { title: 'Block', type: 'block', styles: [ { title: 'Normal', value: 'normal' }, { title: 'H1', value: 'h1' }, { title: 'H2', value: 'h2' }, { title: 'H3', value: 'h3' }, { title: 'Quote', value: 'blockquote' }, ], lists: [{ title: 'Bullet', value: 'bullet' }, {title: 'Number', value: 'number' }], marks: { decorators: [ { title: 'Strong', value: 'strong' }, { title: 'Emphasis', value: 'em' }, { title: 'Code', value: 'code' }, { title: 'Underline', value: 'underline' }, { title: 'Strike', value: 'strike-through' }, { title: 'Superscript', value: 'sup' }, // Added superscript { title: 'Subscript', value: 'sub' }, // Added subscript ], annotations: [ // Annotations remain unchanged ] } } ]}
What we've done here is add two new decorators to the existing list:
{ title: 'Superscript', value: 'sup' }
for superscript text{ title: 'Subscript', value: 'sub' }
for subscript textThe value
properties ('sup' and 'sub') are important as they'll be used as the mark names in the Portable Text structure and correspond to the HTML tags we'll use for rendering.
Now that we've configured Sanity Studio, we need to ensure that our superscript and subscript formatting renders correctly on the frontend. The approach varies depending on the frontend framework you're using, but here's how to do it with the @portabletext/react
library:
First, install the necessary package if you haven't already:
npm install @portabletext/react
Then, create a serializer configuration for your Portable Text content:
// In your frontend code (React example)import { PortableText as SanityPortableText } from '@portabletext/react'// Define custom serializers for our marksconst components = { marks: { sup: ({ children }) => <sup>{children}</sup>, sub: ({ children }) => <sub>{children}</sub>, // Include other mark serializers as needed }, // Include other component serializers as needed}// In your component that renders Sanity contentconst PortableText = ({ blockContent }) => { return ( <div className="content"> <SanityPortableText value={blockContent} components={components} /> </div> )}
This configuration tells the Portable Text renderer how to handle our custom 'sup' and 'sub' marks, transforming them into the appropriate HTML elements.
If you're using Next.js with Sanity, the setup might look like this:
import { PortableText as SanityPortableText } from '@portabletext/react'import { client } from '../lib/sanity'// Define custom serializersconst components = { marks: { sup: ({ children }) => <sup>{children}</sup>, sub: ({ children }) => <sub>{children}</sub>, }}// Fetch and render contentexport default function Page({ pageData }) { return ( <main> <h1>{pageData.title}</h1> <div className="content"> <SanityPortableText value={pageData.content} components={components} /> </div> </main> )}export async function getStaticProps() { const pageData = await client.fetch(` *[_type == "page" && slug.current == "my-page"][0]{ title, content } `) return { props: { pageData } }}
Now it's time to test your implementation to make sure everything is working correctly:
Screenshot: Sanity Studio with superscript and subscript decorators in the formatting toolbar
Symptoms: You've added the decorators to your schema, but they don't appear in the formatting toolbar in Sanity Studio.
Solution: Check that you've:
// Double-check your schema configurationmarks: { decorators: [ // Existing decorators... { title: 'Superscript', value: 'sup' }, // Make sure these are added { title: 'Subscript', value: 'sub' }, ]}
Symptoms: The decorators work fine in Sanity Studio, but the formatting doesn't show up on your website.
Solution: Verify that:
sup
and sub
stylingSymptoms: The superscript and subscript text looks different in various browsers.
Solution: Add consistent CSS styling to normalize the appearance:
/* Add this to your global CSS */sup, sub { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline;}sup { top: -0.5em;}sub { bottom: -0.25em;}
Symptoms: The Sanity editor becomes slow after adding custom decorators.
Solution: This is rare but could happen if you have many custom components or complex rendering logic. Simplify your custom components and make sure they're optimized for performance.
You can enhance the user experience by adding keyboard shortcuts for your new decorators. This is done by modifying your block editor configuration:
const customStyles = { // Existing configuration... decorators: [ // Existing decorators... { title: 'Superscript', value: 'sup', component: SuperscriptDecorator, icon: SuperscriptIcon, hotkey: 'mod+shift+.' // Ctrl/Cmd + Shift + . }, { title: 'Subscript', value: 'sub', component: SubscriptDecorator, icon: SubscriptIcon, hotkey: 'mod+shift+,' // Ctrl/Cmd + Shift + , } ]}
Now that you have superscript and subscript capabilities in your Sanity CMS, here are some practical uses:
Display chemical formulas correctly:
Mathematical expressions:
Add proper trademark and registered symbols:
Create properly formatted citations and footnotes:
Display measurements and units correctly:
In this tutorial, you've learned how to extend Sanity CMS with custom superscript and subscript text decorators. We've covered:
These enhancements will allow your content creators to produce more sophisticated and properly formatted content for scientific, academic, legal, and technical purposes.
ContentWrap can simplify your CMS migration, with proven strategies on reducing costs, transferring data, and designing studios.
Now that you understand how to add custom decorators, you might want to explore:
For more information, check out these resources:
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.
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.
Frustrated by Sanity's inability to wrap text around images? This tutorial shows you how to build a custom component that gives your content editors the power to float images left or right with adjustable width controls — just like Webflow.