How to override annotation styling in Portable Text Editor without losing functionality?

10 replies
Last updated: May 26, 2023
Hi all! We're using three kinds of annotation for a particular document: internal links, external links, and defined terms. In the Portable Text Editor, I would like to render the annotated text with a different color for each kind of annotation. I've built custom annotation preview components, but I can't figure out how to override the styling given by
renderDefault
.
So, to put it simply, how can I override the styling given by
{props.renderDefault(props.children)}
without losing functionality?
AI Update

The trick to overriding the default styling from renderDefault while keeping its functionality is to wrap the rendered output in a custom element (like a span) with your own CSS classes. The renderDefault function applies Sanity's default styles to its children, but you can target those children with more specific CSS selectors to override them.

Here's the approach that works:

1. Create a custom annotation component that wraps renderDefault:

import { BlockAnnotationProps } from 'sanity'
import styles from './CustomAnnotation.module.css'

export const CustomAnnotationRenderer = (props: BlockAnnotationProps) => (
  <span className={styles.customAnnotation}>{props.renderDefault(props)}</span>
)

2. Use CSS to target the children elements and override Sanity's default styles:

The key is using the child selector (*) to target the elements that renderDefault creates. You'll also want to use !important to ensure your styles override Sanity's defaults. Here's an example that changes colors based on theme:

*[data-scheme='light'] .customAnnotation * {
  border-color: #c123fc !important;
  background-color: #f8e9fe;
}

*[data-scheme='dark'] .customAnnotation * {
  border-color: #d97bfd !important;
  background-color: #331440;
  color: #fff;
}

3. Register the custom component in your schema:

defineField({
  name: 'internalLink',
  title: 'Internal Link',
  type: 'reference',
  to: [{ type: 'page' }],
  components: {
    annotation: CustomAnnotationRenderer,
  },
})

For your use case with three different annotation types (internal links, external links, and defined terms), you'd create three separate renderer components, each with its own CSS module defining different colors. The wrapper span approach lets you scope your styles while renderDefault handles all the editor functionality like click interactions, hover states, and accessibility features.

You can inspect the rendered elements in your browser's developer tools to see exactly what HTML structure renderDefault creates, which helps you write more targeted CSS selectors if needed.

This solution comes from this community discussion where someone had the exact same need. For more context on customizing Portable Text annotations, check out the ultimate guide for customizing Portable Text.

Show original thread
10 replies
Hey
user B
. As a quick workaround, I'd suggest you wrap your
props.renderDefault(props.children)
with e.g. a span marked with a dataset attribute (e.g. data-annotation) and use props.renderDefault inside it, so you can override styling.
This might also help
Definitely go with Saskia's way if you can. It's a much more robust solution
Your way is the right way, I just wanted to drop my guide here as a help! :saluting_face:
Thanks
user J
and
user Z
!
user B
I am curious, did you manage to get it to work? Because I dont and I can not find any thread confirming render text in a color πŸ™‚
Hey, I did! I'll share the code in just a bit!
That would be wonderful
user B
πŸ™‚
I am sooo curious πŸ™‚
user B
Hey
user B
β€” soooooo sorry for the slow reply! Here's what I got to work:
I have a simple component that wraps the annotation in a
span
tag with a custom class.
import { BlockAnnotationProps } from 'sanity'
import styles from './DefinitionAnnotationRenderer.module.css'

export const DefinitionAnnotationRenderer = (props: BlockAnnotationProps) => (
  <span className={styles.defAnnotation}>{props.renderDefault(props)}</span>
)
It imports a simple css module that applies styling to the children of the custom wrapper (use the inspector in your browser for more detail). It also accounts for the theme.

*[data-scheme='light'] .defAnnotation * {
  border-color: #c123fc !important;
  background-color: #f8e9fe;
}

*[data-scheme='dark'] .defAnnotation * {
  border-color: #d97bfd !important;
  background-color: #331440;
  color: #fff;
}
In the schema definition, I then pull in the custom renderer component like so:

defineField({
 name: 'definitionAnnotation',
 title: 'Defined Term',
 type: 'reference',
 icon: GoBook,
 to: [{ type: 'definitionDoc' }],
 components: {
   annotation: DefinitionAnnotationRenderer,
 },
}),

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?