Skip to content
Sanity
    • Platform

      Sanity Studio

      Flexible editing environment

      APIs

      Connect to anything

      Content Lake

      Real-time database

      Try product demo

      Features

      Real-time collaboration

      Fearlessly work with content

      Precise content querying

      Treat content as data with GROQ

      Localization

      Coherent messaging across territories

    • Use cases

      E-commerce

      Richer shopping experiences

      Marketing sites

      Control your story

      Products & services

      Innovate and automate

      Mobile apps

      Content backend for every OS

      View all

      Integrations

      Shopify
      Mux
      Vercel
      Netlify
      Algolia
      Cloudinary
      BigCommerce
      Commerce Layer
      Smartling
      Transifex
      View all
    • Learn

      Documentation
      Studio API Reference
      API reference
      Guides
      GROQ cheat sheet
      Sanity UI
      Get started

      Build and share

      Templates
      Tools and plugins
      Schemas and snippets
      Project showcase
      Share your work
      Browse Exchange

      Frameworks

      React
      Vue
      Next.js
      Nuxt.js
      Svelte
      Remix
      Gatsby
      Astro
      Angular
      Eleventy
      View all
    • Discover

      Blog
      Resource library
      Agency partners
      Become a partner
      Technical support
      Talk to sales

      Case studies

      Puma

      Source of truth for global markets

      Aether

      Unique digital shopping experience

      Morning Brew

      Omnichannel media distribution

      InVision

      Delivering exceptional customer experiences

      View all

      Popular guides

      Headless CMS
      Structured content
      Content modeling
      Headless SEO
      Static websites
      View all
    • Enterprise
    • Pricing
    • Log in
    • Contact sales
    • Get started
Contact salesGet started
Published December 20th 2018

Introducing the new Editor for Portable Text

With the new editor for Portable Text, developers get a pocket full of new features for configuring and customizing a productive editing environment for deeply typed content.

Even Eidsten Westvang

Sanity.io co-founder and product person

Central to Sanity is the ability to work with content with all the advantages of treating it as data. Non-obviously perhaps, that makes us pretty obsessed with text. We are typing words into Sanity everyday when we're writing blog posts, documentation, and developing new features.

Treating text as data is super hard. For authors, it needs to look like a modern text editor. This while developers design flexible schemas that combine text with embedded objects and where the end result is data that you can subject to queries and joins.

In our latest release, we ship a new editor for Portable Text. On the surface it may not seem that much has happened, but much work has happened on its underpinnings and machinery. We're super proud of our team that has spent hours developing, testing, finding edge cases (oh, the edge cases!), and obsessing over details.

Run this line in your command line to upgrade and get the new editor in your Sanity project:

npm i -g @sanity/cli && sanity upgrade

What we now have is an editor that works as expected out of the box, but gives you extensive possibilities for configuration and customization. Most of it is done in the simple JavaScript configuration you might already know, while previews, toolbar icons, and block actions can be customized with React components. So let's dive into some of the new things!

Have it your own style

The editor for Portable Text comes with a set of styles that translate well to those you'll find in HTML. You might not be targeting HTML though. While it has always been possible to add and configure block styles, you can now also configure how these styles render in the editor using markup and CSS modules. This means you can tune your editor to be aligned with your organization’s design system.

A custom title set in Garamond

Total render control!

One of the big advantages of Portable Text is the ability to add marks, that is, to annotate inline text with simple keys or complex typed content structures. We have now made it easy to add your own icons via the schema configuration. You can pass in simple strings, or React components (yeah, you can theoretically render a video in your toolbar – but you probably shouldn't). You can also control how marks in the inline text are rendered, for example, if you want to add highlight functionality.

import { HighlightIcon, Highlight } from './decorators/Highlight'

marks: {
  decorators: [
    {title: 'Strong', value: 'strong'},
    {title: 'Emphasis', value: 'em'},
    {title: 'Code', value: 'code'},
    {
      title: 'Highlight',
      value: 'highlight',
      blockEditor: {
        icon: HighlightIcon,
        render: Highlight
      }
    }
  ],
  ...
}

And in ./decorators/Highlight.js:

import React, {Fragment} from 'react'
import PropTypes from 'prop-types'
import styles from './Highlight.css'


const HighlightIcon => {
  return <Fragment>🖍</Fragment>
}

const Highlight = props => {
  return <span className={styles.root}>{props.children}</span>
}

Highlight.propTypes = {
  children: PropTypes.node.isRequired
}

export default {
  Highlight,
  HighlightIcon
}

Tailored paste handling

Now, this is pretty cool. We left you hooks to override and tune how content should be pasted into the editor. The editor already knows to deal with a lot of weird edge cases from Word documents. And it deals copy-pasted text from Google Docs too. But perhaps you have a legacy system with some special markup, or want to make it easy for editors to be able to copy paste content from the old website into the editor and have some parts be transformed into some custom content blocks.

An experiment we did with pasting markdown into the editor

We also updated the tool for working programmatically with Portable Text to make it easier to design customizations like this.

Validations and Actions

If you add validation rules to your markers, the warnings will now be highlighted in the margin, and your editors can activate the annotation modal directly from the validation menu. Perhaps you want to make sure that all images have got an alternative text, or warn editors if they use the http:// and not https:// URL in a link.

With Actions you can pass the content of a block to a React component that renders in the margin. This can be useful if you want to create workflows that triggers on certain strings or content types. You can, for example, make a thing that looks at your text for product names, and makes it easy to generate an internal reference when they appear.

Open Sourced Specification of Portable Text

It has always been a core idea of Sanity to have deeply typed content. Rich text is too often just left in the hands of markdown or HTML. While they're two perfectly good formats for what they're made for, they have no business as the storage format in a modern content backend.

We needed something that made it possible to have nested typed content structures, while being nimble for a WYSIWYG editor and that could be serialized into whatever markup or data structure you needed it for, without to much hassle. We wanted it to be JSON out of the box, and not leave the burden of transpiling XML on our users. That's how Portable Text was born. Since we find it mighty useful, we wanted to share it with the world. On www.portabletext.org you can find the specification and links to various tooling. We think it would have been super fun if someone decided to adapt it to their own use case.

Wrap it!

In order to add your own custom marker renderers, or actions, you have to pass those as props into the editor. It's fairly straight forward if you know some React:

import React from 'react'
import {BlockEditor} from 'part:@sanity/form-builder'
import renderCustomMarkers from '../renderCustomMarkers'

export default class ArticleBlockEditor extends React.PureComponent {
  render() {
    return (
      <div>
        <BlockEditor
          {...this.props}
          renderCustomMarkers={renderCustomMarkers}
        />
      </div>
    )
  }
}

A byproduct of this is of course that it's easy to wrap the editor in whatever you may desire. Be it real-time text statistics (you have access to the whole document in this.props) or perhaps be able to send the text to a text-to-speech API as we did with our SSML editor.

No more locking

With this upgrade, all fields in the Sanity Studio have support for real-time collaboration. You can sit on your desktop hammering away, while your co-worker fixes your spelling errors at the same time.

There's still a caveat – we still need to correctly handle the caret positions of users writing within the same paragraph. Annoyingly your caret position resets to the top of the block when other patches are applied to the same block. We'll be working more on collaboration in the new year and this is near the top of lists of things to improve.

The last large upgrade in an amazing year!

We hope you feel our enthusiasm for this rich text editor when you try it out. It seems like an appropriate occasion to note the passing of Evelyn Berezin, the creator of the first word processor, who sadly passed away at 93. We all owe Evelyn our gratitude for her efforts in paving the road and launching a revolution for all of us who friviously type on digital sheets.

This will be the last large feature upgrade in 2018. But since the calendar shows December 20th, we feel that it is perhaps reasonable. It has been an absolutely amazing year for us at Sanity HQ! We look forward to seeing you after the holidays and tell you some other exciting things we have been working on lately with some of our friends.

Page content

  • Have it your own style
  • Total render control!
  • Tailored paste handling
  • Validations and Actions
  • Open Sourced Specification of Portable Text
  • Wrap it!
  • No more locking
  • The last large upgrade in an amazing year!

Product

Sanity StudioAPIsContent LakeSecurity & Compliance
  • Sanity vs Contentful
  • Sanity vs Strapi
  • Sanity vs Wordpress
  • Sanity vs Adobe Experience Manager
  • Sanity vs Hygraph
  • Sanity vs Sitecore
  • Sanity vs Storyblok
  • Sanity vs Contentstack
  • Sanity vs Prismic
  • Sanity vs Drupal
  • Sanity vs ButterCMS

Resources

DocumentationBlogResource libraryCase Studies
  • React Blog
  • Gatsby Blog
  • Next.js Landing Pages
  • Making a PWA
  • Single Page Application
  • Svelte & Typescript App
  • Vue & Tailwind Blog
  • Developer Portfolio Templates
  • Form validation with Yup
  • Live Preview with Next.js and Sanity.io
  • Next.js blog
  • Next.js personal website
  • Clean Next.js + Sanity app
  • Clean Remix + Sanity app
  • Clean SvelteKit + Sanity app
  • All Templates
  • Agency partners
  • Technology partners
  • Headless CMS 101
  • Static Sites 101
  • Headless Commerce 101
  • CMS for enterprise
  • Headless SEO
  • Localization
  • Content as a Service
  • What is a DXP?
  • Typescript 101
  • Ecommerce SEO
  • What is a Composable DXP?
  • What is an API?
  • GraphQL vs REST
  • React CMS
  • Next.JS CMS
  • CMS for Shopify
  • API-first CMS
  • Content platform
  • Multilingual CMS
  • Static Site CMS
  • Gatsby CMS
  • Node CMS
  • E-commerce CMS
  • Vue CMS
  • Angular CMS
  • GraphQL CMS
  • Newspaper CMS
  • Magazine CMS
  • CMS for apps
  • Remix CMS
  • Nuxt CMS
  • SvelteKit CMS
  • Agile CMS
  • Eleventy CMS

Company

Contact SalesEnterpriseCareersTerms of ServicePrivacy PolicyAccessibility Statement

Stay connected

  • GitHub
  • Slack
  • Twitter
  • YouTube
  • Stack Overflow
  • Blog RSS
  • Newsletter
©Sanity 2023