Skip to content

🚧 Nested Marks

Nested marks allow you to create rich, hierarchical text structures where marks can contain other marks. This enables complex formatting scenarios like markdown-style text, HTML-like tags, and multi-level mark structures.

Flat marks (__value__): Content is plain text, nested patterns are ignored.

markup: '@[__value__]'
value: '@[Hello *world*]'
// Result: One mark with value = "Hello *world*" (literal asterisks)

Nested marks (__nested__): Content can contain other marks.

markup: '*__nested__*'
value: '*Hello **world***'
// Result: Italic mark containing "Hello " + bold mark "world"
Feature__value____nested__
Content TypePlain textSupports child marks
ParsingNo recursive parsingRecursive parsing
Propsvalue stringchildren ReactNode + nested string
Use CaseSimple marksHierarchical structures

Use the __nested__ placeholder instead of __value__:

// ✅ Supports nesting
const NestedMarkup = '**__nested__**'
// ❌ Does not support nesting
const FlatMarkup = '**__value__**'

Example: Markdown-style formatting

import {MarkedInput} from 'rc-marked-input'
import {useState} from 'react'
const FormatMark = ({children, nested}) => {
// For nested marks, children is ReactNode
// For flat marks, nested is string
return <span>{children || nested}</span>
}
function MarkdownEditor() {
const [value, setValue] = useState('This is **bold with *italic* inside**')
return (
<MarkedInput
value={value}
onChange={setValue}
Mark={FormatMark}
options={[
{
markup: '**__nested__**',
slotProps: {
mark: ({children}) => ({
children,
style: {fontWeight: 'bold'},
}),
},
},
{
markup: '*__nested__*',
slotProps: {
mark: ({children}) => ({
children,
style: {fontStyle: 'italic'},
}),
},
},
]}
/>
)
}

When using __nested__, your Mark component receives:

interface MarkProps {
value?: string // undefined for nested marks
meta?: string // Metadata (if __meta__ used)
nested?: string // Raw nested content as string
children?: ReactNode // Rendered nested children (use this!)
}
PropTypeDescriptionWhen to Use
childrenReactNodeRendered child marksRecommended - for rendering
nestedstringRaw text contentEdge cases - for processing raw text

Example:

function NestedMark({children, nested}) {
// ✅ Recommended: Use children
return <strong>{children}</strong>
// ⚠️ Edge case: Use nested for raw text
// return <strong>{nested?.toUpperCase()}</strong>
}
import {MarkedInput} from 'rc-marked-input'
import {useState} from 'react'
function SimpleMark({children, style}) {
return <span style={style}>{children}</span>
}
function SimpleNested() {
const [value, setValue] = useState('Text with **bold and *italic* formatting**')
return (
<MarkedInput
value={value}
onChange={setValue}
Mark={SimpleMark}
options={[
{
markup: '**__nested__**',
slotProps: {
mark: ({children}) => ({
children,
style: {fontWeight: 'bold'},
}),
},
},
{
markup: '*__nested__*',
slotProps: {
mark: ({children}) => ({
children,
style: {fontStyle: 'italic'},
}),
},
},
]}
/>
)
}

Output for '**bold *italic***':

<span style="font-weight: bold">
bold
<span style="font-style: italic">italic</span>
</span>

ParserV2 supports two values patterns where opening and closing tags must match:

markup: '<__value__>__nested__</__value__>'

How it works:

  • Pattern contains exactly two __value__ placeholders
  • Both values must be identical to match
  • Perfect for HTML/XML-like structures
import {MarkedInput} from 'rc-marked-input'
import {useState} from 'react'
function HtmlLikeMark({value, children, nested}) {
// Use value as the HTML tag name
const Tag = (value || 'span') as React.ElementType
return <Tag>{children || nested}</Tag>
}
function HtmlEditor() {
const [value, setValue] = useState(
'<div>Container with <mark>highlighted text</mark> and <b>bold with <i>italic</i></b></div>'
)
return (
<MarkedInput
value={value}
onChange={setValue}
Mark={HtmlLikeMark}
options={[{markup: '<__value__>__nested__</__value__>'}]}
/>
)
}

Matching rules:

// ✅ Valid - tags match
'<div>content</div>'
'<span>content</span>'
// ❌ Invalid - tags don't match
'<div>content</span>' // Won't be recognized
'<b>content</i>' // Won't be recognized
// BBCode-style
markup: '[__value__]__nested__[/__value__]'
// Matches: [b]text[/b], [i]text[/i]
// Template tags
markup: '{{__value__}}__nested__{{/__value__}}'
// Matches: {{section}}content{{/section}}
// Custom brackets
markup: '<<__value__>>__nested__<</value__>>'
// Matches: <<tag>>content<</tag>>

Marks can be nested to any depth:

'**bold with *italic with ~~strikethrough~~***'
// Renders as:
<bold>
bold with
<italic>
italic with
<strikethrough>strikethrough</strikethrough>
</italic>
</bold>

Example: Multi-level formatting

function MultiLevelEditor() {
const [value, setValue] = useState('Normal **bold *italic ~~strike !!highlight!!~~***')
return (
<MarkedInput
value={value}
onChange={setValue}
Mark={({children}) => <span>{children}</span>}
options={[
{
markup: '**__nested__**',
slotProps: {
mark: ({children}) => ({
children,
style: {fontWeight: 'bold'},
}),
},
},
{
markup: '*__nested__*',
slotProps: {
mark: ({children}) => ({
children,
style: {fontStyle: 'italic'},
}),
},
},
{
markup: '~~__nested__~~',
slotProps: {
mark: ({children}) => ({
children,
style: {textDecoration: 'line-through'},
}),
},
},
{
markup: '!!__nested__!!',
slotProps: {
mark: ({children}) => ({
children,
style: {background: 'yellow'},
}),
},
},
]}
/>
)
}

Use useMark() hook to access nesting details:

import {useMark} from 'rc-marked-input'
function NestedAwareMark({children}) {
const {depth, hasChildren, parent, children: tokens} = useMark()
return (
<div style={{marginLeft: depth * 20, border: '1px solid #ccc'}}>
<div>
Depth: {depth} | Has children: {hasChildren ? 'Yes' : 'No'} | Parent: {parent?.value || 'None'}
</div>
<div>{children}</div>
</div>
)
}
PropertyTypeDescription
depthnumberNesting level (0 = root)
hasChildrenbooleanWhether mark has nested children
parentMarkToken | undefinedParent mark token
childrenToken[]Array of child tokens

Example: Collapsible nested structure

function CollapsibleMark({children}) {
const {label, hasChildren, depth} = useMark()
const [collapsed, setCollapsed] = useState(false)
if (!hasChildren) {
return <span>{label}</span>
}
return (
<div style={{marginLeft: depth * 20}}>
<button onClick={() => setCollapsed(!collapsed)}>{collapsed ? '' : ''}</button>
<strong>{label}</strong>
{!collapsed && <div className="children">{children}</div>}
</div>
)
}

Combine __nested__ with __meta__:

markup: '@[__nested__](__meta__)'

Example: Colored nested text

function ColoredMark({children, meta}) {
const colors = {
red: '#ffebee',
blue: '#e3f2fd',
green: '#e8f5e9',
}
return <span style={{background: colors[meta] || 'transparent'}}>{children}</span>
}
// Usage
;<MarkedInput
value="@[Red text with **bold**](red) and @[Blue](blue)"
Mark={ColoredMark}
options={[
{markup: '@[__nested__](__meta__)'},
{
markup: '**__nested__**',
slotProps: {
mark: ({children}) => ({
children,
style: {fontWeight: 'bold'},
}),
},
},
]}
/>
import {MarkedInput} from 'rc-marked-input'
import {useState} from 'react'
function MarkdownMark({children, nested}) {
return <span>{children || nested}</span>
}
function MarkdownEditor() {
const [value, setValue] = useState('This is **bold**, this is *italic*, and this is **bold with *italic* inside**.')
return (
<MarkedInput
value={value}
onChange={setValue}
Mark={MarkdownMark}
options={[
{
markup: '**__nested__**',
slotProps: {
mark: ({children}) => ({
children,
style: {fontWeight: 'bold'},
}),
},
},
{
markup: '*__nested__*',
slotProps: {
mark: ({children}) => ({
children,
style: {fontStyle: 'italic'},
}),
},
},
]}
/>
)
}
function HtmlTagMark({value, children}) {
// Map tag names to React components or HTML elements
const tagMap: Record<string, React.ElementType> = {
div: 'div',
span: 'span',
p: 'p',
b: 'strong',
i: 'em',
mark: 'mark',
code: 'code',
}
const Tag = tagMap[value || 'span'] || 'span'
return <Tag>{children}</Tag>
}
function HtmlEditor() {
const [value, setValue] = useState('<div>Article with <mark>highlighted <b>bold</b> text</mark></div>')
return (
<MarkedInput
value={value}
onChange={setValue}
Mark={HtmlTagMark}
options={[{markup: '<__value__>__nested__</__value__>'}]}
/>
)
}
function BBCodeMark({value, children}) {
const styles: Record<string, React.CSSProperties> = {
b: {fontWeight: 'bold'},
i: {fontStyle: 'italic'},
u: {textDecoration: 'underline'},
color: {color: 'red'},
size: {fontSize: '20px'},
}
return <span style={styles[value || '']}>{children}</span>
}
function BBCodeEditor() {
const [value, setValue] = useState('[b]Bold [i]and italic[/i][/b] with [color]red text[/color]')
return (
<MarkedInput
value={value}
onChange={setValue}
Mark={BBCodeMark}
options={[{markup: '[__value__]__nested__[/__value__]'}]}
/>
)
}

Nested marks create more React elements:

// Flat: 1 mark = 1 React element
'@[simple]'<Mark>simple</Mark>
// Nested: Multiple React elements
'**bold *italic***'<Mark><Mark>italic</Mark></Mark>

Optimization tips:

  1. Memoize Mark component:
const Mark = React.memo(({children}) => <span>{children}</span>)
  1. Limit nesting depth for large documents:
// Set reasonable max depth in parser config (if supported)
maxDepth: 5
  1. Use flat marks when possible:
// If you don't need nesting, use __value__
markup: '@[__value__]' // Faster than __nested__

Deep nesting requires recursive parsing:

// Fast: 1 level
'**bold**'
// Slower: 5 levels
'**a *b ~~c __d !!e!!__~~***'

Best practices:

  • Avoid unnecessarily deep nesting (>5 levels)
  • Use flat marks for simple cases
  • Profile with React DevTools for large documents
// Use children for rendering
<span>{children}</span>
// Provide fallback for non-nested
<span>{children || nested}</span>
// Memoize expensive Mark components
const Mark = React.memo(({ children }) => <span>{children}</span>)
// Type your component properly
function Mark({ children }: { children?: ReactNode }) {
return <span>{children}</span>
}
// Don't modify children directly
function Bad({children}) {
return <span>{children.toUpperCase()}</span> // Error!
}
// Don't use value with __nested__
function Bad({value}) {
return <span>{value}</span> // value is undefined!
}
// Don't create infinite loops
markup: '**__nested__**'
value: '**text **nested****' // Can cause issues

Type your nested Mark components:

import type {ReactNode} from 'react'
interface NestedMarkProps {
value?: string
meta?: string
nested?: string
children?: ReactNode // Important for nested marks
}
function TypedMark({children, nested}: NestedMarkProps) {
return <span>{children || nested}</span>
}

For HTML-like tags:

interface HtmlMarkProps {
value?: string // Tag name
children?: ReactNode
}
function HtmlMark({value, children}: HtmlMarkProps) {
const Tag = (value || 'span') as React.ElementType
return <Tag>{children}</Tag>
}

Key Takeaways:

  • Use __nested__ placeholder for hierarchical structures
  • Render children prop in your Mark component
  • Two values pattern (<__value__>...</__value__>) for matching tags
  • Access nesting info with useMark() hook
  • Optimize with React.memo for better performance