How to model adjacent `<kbd>` tags in Sanity without auto-merging?
You're experiencing a classic Portable Text behavior! The issue is that decorators (marks) automatically merge when adjacent, which is exactly what you're seeing with your <kbd> tags. For keyboard shortcuts where you need <kbd>Cmd</kbd><kbd>i</kbd> to remain separate, you should indeed use annotations or inline objects instead.
Here are your two best options:
Option 1: Use Annotations (simpler)
Annotations are marks that can carry additional data and don't auto-merge like decorators. Define a kbd annotation in your schema:
defineType({
name: 'blockContent',
type: 'array',
of: [
{
type: 'block',
marks: {
annotations: [
{
name: 'kbd',
type: 'object',
title: 'Keyboard Key',
fields: [
{
name: 'key',
type: 'string',
title: 'Key'
}
]
}
]
}
}
]
})Annotations won't merge when adjacent because each one is a distinct object with its own data. However, they still wrap text spans, so you'd select text and apply the annotation.
Option 2: Use Inline Objects (recommended for your case)
For your use case, inline objects are probably the better choice since keyboard keys are really discrete objects, not text formatting. According to the Portable Text documentation, inline blocks let you embed custom content within text paragraphs by adding them to the of array within the block type:
defineType({
name: 'kbd',
type: 'object',
title: 'Keyboard Key',
fields: [
{
name: 'key',
type: 'string',
title: 'Key'
}
]
})
// Then in your block content:
defineType({
name: 'blockContent',
type: 'array',
of: [
{
type: 'block',
of: [
{ type: 'kbd' } // Add as inline object
]
}
]
})With inline objects, each <kbd> becomes a distinct object in the content array, so they'll never merge. This also makes them much easier to insert programmatically during your markdown import.
For Your Import Script
When importing, inline objects would be easier to work with since you can insert them as distinct objects in the children array:
{
_type: 'block',
children: [
{ _type: 'span', text: 'Press ' },
{ _type: 'kbd', _key: 'unique-key-1', key: 'Cmd' },
{ _type: 'kbd', _key: 'unique-key-2', key: 'i' },
{ _type: 'span', text: ' to italicize' }
]
}The inline objects approach is probably your best bet here since keyboard shortcuts are semantic units rather than text styling. This aligns with how Portable Text separates content structure from presentation, and it will make both authoring and rendering much cleaner. Each keyboard key will be a first-class object that you can style and render independently on your frontend.
Show original thread16 replies
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.