Create reusable content blocks like CTAs, forms, and callouts.
Snippets insert placeholder tags like [CTA] into your content. Your frontend code parses these and renders custom components.
[CTA] to your contentCTA.jsx component in your projectShould match your component file. If you have CTA.jsx, name it "CTA". This helps you remember which file handles each snippet.
The text inserted into your content, e.g., [CTA]. Auto-generated from the name, but you can customize it.
Optional fields you can fill in per-article. When inserting, you'll be asked for values like[CTA title="My Title" href="/signup"].
In the editor toolbar, click Snippets → + New and add:
CTA<p>[CTA]</p>
Add props directly in the placeholder:
<p>[CTA variant="minimal" title="Subscribe" buttonText="Join"]</p>
This example uses Next.js, but the same pattern works with any React framework or even Vue/Svelte with equivalent code.
// src/app/blog/components/CTA.jsx
'use client';
import Link from 'next/link';
export default function CTA({
title = "Ready to get started?",
description = "Join thousands of users today.",
buttonText = "Get Started",
buttonHref = "/register",
variant = "default" // "default" | "minimal" | "gradient"
}) {
const styles = {
default: "bg-gray-900 text-white",
minimal: "bg-gray-50 border border-gray-200",
gradient: "bg-gradient-to-r from-gray-900 to-gray-700 text-white",
};
return (
<div className={`rounded-xl p-8 my-8 text-center ${styles[variant]}`}>
<h3 className="text-xl font-medium mb-2">{title}</h3>
<p className="mb-6 opacity-80">{description}</p>
<Link
href={buttonHref}
className="inline-block px-6 py-3 bg-white text-gray-900 rounded-lg font-medium hover:bg-gray-100"
>
{buttonText}
</Link>
</div>
);
}// In your blog/[slug]/page.js
import CTA from "../components/CTA";
function BlogContent({ content }) {
// Pattern to match [CTA] placeholders
const CTA_PATTERN = /<p>\s*\[CTA(?:\s+([^\]]*))?\]\s*<\/p>|\[CTA(?:\s+([^\]]*))?\]/gi;
if (!CTA_PATTERN.test(content)) {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
CTA_PATTERN.lastIndex = 0;
const parts = [];
let lastIndex = 0;
let match;
while ((match = CTA_PATTERN.exec(content)) !== null) {
// Add content before placeholder
if (match.index > lastIndex) {
parts.push(
<div key={lastIndex} dangerouslySetInnerHTML={{ __html: content.slice(lastIndex, match.index) }} />
);
}
// Parse props from placeholder
const props = {};
const variantMatch = match[0].match(/variant="([^"]*)"/);
const titleMatch = match[0].match(/title="([^"]*)"/);
if (variantMatch) props.variant = variantMatch[1];
if (titleMatch) props.title = titleMatch[1];
// ... parse other props
parts.push(<CTA key={match.index} {...props} />);
lastIndex = match.index + match[0].length;
}
if (lastIndex < content.length) {
parts.push(<div key={lastIndex} dangerouslySetInnerHTML={{ __html: content.slice(lastIndex) }} />);
}
return <>{parts}</>;
}[CTA]Promote signups, products, or actions
[NEWSLETTER]Email signup form
[PRODUCT id="123"]Display product details from your database
[RELATED]Show related articles
[TOC]Auto-generated from headings
[NAME] or [NAME prop="value"]<p>[CTA]</p> — wrapped in paragraph for editor compatibility[CTA] in your content