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:
1// Default decorators in Sanity
2decorators: [
3 { title: 'Strong', value: 'strong' },
4 { title: 'Emphasis', value: 'em' },
5 { title: 'Code', value: 'code' },
6 { title: 'Underline', value: 'underline' },
7 { title: 'Strike', value: 'strike-through' },
8]
When a decorator is applied to text, it gets stored in the content's JSON structure like this:
1{
2 "_type": "block",
3 "children": [
4 {
5 "_type": "span",
6 "text": "This is ",
7 "marks": []
8 },
9 {
10 "_type": "span",
11 "text": "bold",
12 "marks": ["strong"]
13 },
14 {
15 "_type": "span",
16 "text": " text",
17 "marks": []
18 }
19 ]
20}
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:
1// Before: Default block content schema
2export default {
3 title: 'Block Content',
4 name: 'blockContent',
5 type: 'array',
6 of: [
7 {
8 title: 'Block',
9 type: 'block',
10 styles: [
11 { title: 'Normal', value: 'normal' },
12 { title: 'H1', value: 'h1' },
13 { title: 'H2', value: 'h2' },
14 { title: 'H3', value: 'h3' },
15 { title: 'Quote', value: 'blockquote' },
16 ],
17 lists: [{title: 'Bullet', value: 'bullet' }, {title: 'Number', value: 'number' }],
18 marks: {
19 decorators: [
20 { title: 'Strong', value: 'strong' },
21 { title: 'Emphasis', value: 'em' },
22 { title: 'Code', value: 'code' },
23 { title: 'Underline', value: 'underline' },
24 { title: 'Strike', value: 'strike-through' },
25 ],
26 annotations: [
27 // Annotations like links would be here
28 ]
29 }
30 }
31 ]
32}
Now, let's modify it to add our new decorators:
1// After: Modified block content schema with superscript and subscript
2export default {
3 title: 'Block Content',
4 name: 'blockContent',
5 type: 'array',
6 of: [
7 {
8 title: 'Block',
9 type: 'block',
10 styles: [
11 { title: 'Normal', value: 'normal' },
12 { title: 'H1', value: 'h1' },
13 { title: 'H2', value: 'h2' },
14 { title: 'H3', value: 'h3' },
15 { title: 'Quote', value: 'blockquote' },
16 ],
17 lists: [{ title: 'Bullet', value: 'bullet' }, {title: 'Number', value: 'number' }],
18 marks: {
19 decorators: [
20 { title: 'Strong', value: 'strong' },
21 { title: 'Emphasis', value: 'em' },
22 { title: 'Code', value: 'code' },
23 { title: 'Underline', value: 'underline' },
24 { title: 'Strike', value: 'strike-through' },
25 { title: 'Superscript', value: 'sup' }, // Added superscript
26 { title: 'Subscript', value: 'sub' }, // Added subscript
27 ],
28 annotations: [
29 // Annotations remain unchanged
30 ]
31 }
32 }
33 ]
34}
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:
1npm install @portabletext/react
Then, create a serializer configuration for your Portable Text content:
1// In your frontend code (React example)
2import { PortableText } from '@portabletext/react'
3
4// Define custom serializers for our marks
5const components = {
6 marks: {
7 sup: ({ children }) => <sup>{children}</sup>,
8 sub: ({ children }) => <sub>{children}</sub>,
9 // Include other mark serializers as needed
10 },
11 // Include other component serializers as needed
12}
13
14// In your component that renders Sanity content
15const MyComponent = ({ blockContent }) => {
16 return (
17 <div className="content">
18 <PortableText
19 value={blockContent}
20 components={components}
21 />
22 </div>
23 )
24}
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:
1// In your Next.js page or component
2import { PortableText } from '@portabletext/react'
3import { client } from '../lib/sanity'
4
5// Define custom serializers
6const components = {
7 marks: {
8 sup: ({ children }) => <sup>{children}</sup>,
9 sub: ({ children }) => <sub>{children}</sub>,
10 }
11}
12
13// Fetch and render content
14export default function Page({ pageData }) {
15 return (
16 <main>
17 <h1>{pageData.title}</h1>
18 <div className="content">
19 <PortableText
20 value={pageData.content}
21 components={components}
22 />
23 </div>
24 </main>
25 )
26}
27
28export async function getStaticProps() {
29 const pageData = await client.fetch(`
30 *[_type == "page" && slug.current == "my-page"][0]{
31 title,
32 content
33 }
34 `)
35
36 return {
37 props: {
38 pageData
39 }
40 }
41}
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:
1// Double-check your schema configuration
2marks: {
3 decorators: [
4 // Existing decorators...
5 { title: 'Superscript', value: 'sup' }, // Make sure these are added
6 { title: 'Subscript', value: 'sub' },
7 ]
8}
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:
1/* Add this to your global CSS */
2sup, sub {
3 font-size: 75%;
4 line-height: 0;
5 position: relative;
6 vertical-align: baseline;
7}
8
9sup {
10 top: -0.5em;
11}
12
13sub {
14 bottom: -0.25em;
15}
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:
1// In your sanityClient.js or similar file
2const customStyles = {
3 // Existing configuration...
4 decorators: [
5 // Existing decorators...
6 {
7 title: 'Superscript',
8 value: 'sup',
9 component: SuperscriptDecorator,
10 icon: SuperscriptIcon,
11 hotkey: 'mod+shift+.' // Ctrl/Cmd + Shift + .
12 },
13 {
14 title: 'Subscript',
15 value: 'sub',
16 component: SubscriptDecorator,
17 icon: SubscriptIcon,
18 hotkey: 'mod+shift+,' // Ctrl/Cmd + Shift + ,
19 }
20 ]
21}
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.