Block Content Component
Block Content Component
Section titled “Block Content Component”The BlockContent component renders Sanity’s portable text (block content) into HTML. Supports common block types including paragraphs, headings, lists, and text formatting.
Features
Section titled “Features”- ✅ Paragraphs and headings (H1-H6)
- ✅ Text marks (bold, italic, code, underline)
- ✅ Blockquotes
- ✅ Styled with Tailwind classes
- ✅ XSS-safe rendering
- ✅ Prose typography support
- ✅ Custom CSS classes
- ✅ Lightweight (~1 KB)
Basic Usage
Section titled “Basic Usage”---import BlockContent from '@rejected-media/podcast-framework-core/components/BlockContent.astro';
const episode = await getEpisode(Astro.params.slug);---
<BlockContent blocks={episode.showNotes} />Optional Props
Section titled “Optional Props”blocks
Section titled “blocks”Type: any[] (Sanity portable text blocks)
Default: undefined
Array of Sanity block content. Component returns null if not provided.
<BlockContent blocks={episode.showNotes} />Type: string
Default: undefined
Custom CSS classes to apply to the container.
<BlockContent blocks={episode.showNotes} class="prose-lg max-w-4xl"/>Complete Example
Section titled “Complete Example”---import { getEpisode } from '@rejected-media/podcast-framework-core';import BlockContent from '@rejected-media/podcast-framework-core/components/BlockContent.astro';import BaseLayout from '@rejected-media/podcast-framework-core/layouts/BaseLayout.astro';
const episode = await getEpisode(Astro.params.slug);---
<BaseLayout title={episode.title}> <article class="max-w-4xl mx-auto px-4 py-12"> <h1 class="text-4xl font-bold mb-4">{episode.title}</h1>
<p class="text-lg text-gray-600 mb-8"> {episode.description} </p>
<!-- Spotify player --> {episode.spotifyLink && ( <iframe src={getSpotifyEmbedUrl(episode.spotifyLink)} /> )}
<!-- Show Notes --> {episode.showNotes && ( <div class="mt-12"> <h2 class="text-2xl font-bold mb-6">Show Notes</h2> <BlockContent blocks={episode.showNotes} class="prose prose-lg max-w-none" /> </div> )}
<!-- About the Episode --> {episode.about && ( <div class="mt-12"> <h2 class="text-2xl font-bold mb-6">About</h2> <BlockContent blocks={episode.about} class="prose max-w-none" /> </div> )} </article></BaseLayout>Supported Block Types
Section titled “Supported Block Types”Paragraphs
Section titled “Paragraphs”Sanity Input:
{ _type: 'block', style: 'normal', children: [ { text: 'This is a paragraph.' } ]}Rendered HTML:
<p class="text-lg text-gray-700 leading-relaxed mb-6"> This is a paragraph.</p>Headings
Section titled “Headings”Sanity Input:
{ _type: 'block', style: 'h1', children: [ { text: 'Heading Text' } ]}Rendered HTML:
<!-- H1 --><h1 class="text-3xl font-bold mb-4">Heading Text</h1>
<!-- H2 --><h2 class="text-2xl font-bold mb-3">Heading Text</h2>
<!-- H3 --><h3 class="text-xl font-semibold mb-2">Heading Text</h3>
<!-- H4 --><h4 class="text-lg font-semibold mb-2">Heading Text</h4>
<!-- H5 --><h5 class="font-semibold mb-2">Heading Text</h5>
<!-- H6 --><h6 class="font-semibold mb-2 text-sm">Heading Text</h6>Text Formatting
Section titled “Text Formatting”Bold:
{ text: 'bold text', marks: ['strong']}→ <strong>bold text</strong>
Italic:
{ text: 'italic text', marks: ['em']}→ <em>italic text</em>
Code:
{ text: 'code text', marks: ['code']}→ <code class="bg-gray-100 px-1 py-0.5 rounded">code text</code>
Underline:
{ text: 'underlined text', marks: ['underline']}→ <u>underlined text</u>
Blockquotes
Section titled “Blockquotes”Sanity Input:
{ _type: 'block', style: 'blockquote', children: [ { text: 'This is a quote.' } ]}Rendered HTML:
<blockquote class="border-l-4 border-gray-300 pl-4 italic my-4"> This is a quote.</blockquote>Styling
Section titled “Styling”Default Classes
Section titled “Default Classes”The component applies these Tailwind classes:
/* Container */.prose .max-w-none
/* Paragraph */.text-lg .text-gray-700 .leading-relaxed .mb-6
/* Headings */.text-3xl .font-bold .mb-4 /* H1 */.text-2xl .font-bold .mb-3 /* H2 */.text-xl .font-semibold .mb-2 /* H3 */
/* Code */.bg-gray-100 .px-1 .py-0.5 .rounded
/* Blockquote */.border-l-4 .border-gray-300 .pl-4 .italic .my-4Custom Styling
Section titled “Custom Styling”Override with Tailwind prose classes:
<BlockContent blocks={showNotes} class="prose prose-lg prose-blue max-w-none"/>Prose Variants:
prose-sm- Smaller textprose-lg- Larger textprose-xl- Extra large textprose-blue- Blue linksprose-slate- Slate color scheme
Custom CSS
Section titled “Custom CSS”<BlockContent blocks={showNotes} />
<style is:global> .prose p { font-size: 1.125rem; line-height: 1.75; color: #374151; }
.prose h2 { color: #1e40af; border-bottom: 2px solid #e5e7eb; padding-bottom: 0.5rem; }
.prose blockquote { border-left-color: #3b82f6; background-color: #eff6ff; padding: 1rem; }</style>Sanity Schema Example
Section titled “Sanity Schema Example”Configure rich text in your Sanity schema:
export default { name: 'episode', type: 'document', fields: [ // ... other fields { name: 'showNotes', title: 'Show Notes', type: 'array', of: [ { type: 'block', styles: [ { title: 'Normal', value: 'normal' }, { title: 'H1', value: 'h1' }, { title: 'H2', value: 'h2' }, { title: 'H3', value: 'h3' }, { title: 'Quote', value: 'blockquote' } ], marks: { decorators: [ { title: 'Strong', value: 'strong' }, { title: 'Emphasis', value: 'em' }, { title: 'Code', value: 'code' }, { title: 'Underline', value: 'underline' } ] } } ] } ]};Limitations
Section titled “Limitations”Lists Not Yet Supported
Section titled “Lists Not Yet Supported”List rendering is not yet implemented:
{ _type: 'list', // Not supported yet items: [...]}Workaround: Use paragraphs with dashes:
- Item 1- Item 2- Item 3Links Not Yet Supported
Section titled “Links Not Yet Supported”Link marks are not yet implemented:
{ text: 'link text', marks: ['link'] // Not supported yet}Workaround: Include full URLs in text:
Visit https://example.com for more infoImages Not Yet Supported
Section titled “Images Not Yet Supported”Image blocks are not yet implemented:
{ _type: 'image', // Not supported yet asset: {...}}Workaround: Use separate image fields in schema.
Extending the Component
Section titled “Extending the Component”Adding Link Support
Section titled “Adding Link Support”---// Copy framework component and extend
function renderBlock(block: any): string { if (block._type === 'block') { const text = block.children?.map((child: any) => { let content = child.text || '';
// Existing marks... if (child.marks?.includes('strong')) { content = `<strong>${content}</strong>`; }
// Add link support if (child.marks?.includes('link')) { const mark = block.markDefs?.find((m: any) => m._key === child.marks.find((m: string) => m.startsWith('link'))); if (mark?.href) { content = `<a href="${mark.href}" class="text-blue-600 hover:underline">${content}</a>`; } }
return content; }).join('') || '';
// ... rest of rendering }}---Adding List Support
Section titled “Adding List Support”---function renderBlock(block: any): string { // ... existing code
if (block._type === 'list') { const tag = block.style === 'number' ? 'ol' : 'ul'; const items = block.items?.map((item: any) => { return `<li>${item.text}</li>`; }).join('');
return `<${tag} class="list-disc ml-6 mb-4">${items}</${tag}>`; }
return '';}---Accessibility
Section titled “Accessibility”Semantic HTML
Section titled “Semantic HTML”The component renders semantic HTML:
<h1>Heading</h1><p>Paragraph</p><blockquote>Quote</blockquote><strong>Bold</strong><em>Italic</em>Screen Readers
Section titled “Screen Readers”- Headings provide document structure
- Blockquotes announced as quotations
- Code announced as code blocks
Performance
Section titled “Performance”- Bundle Size: ~1 KB
- JavaScript: None (renders at build time)
- Render: Static HTML
- XSS Protection: DOM-based rendering (safe)
Troubleshooting
Section titled “Troubleshooting”Content not rendering
Section titled “Content not rendering”Check that blocks array exists:
{episode.showNotes ? ( <BlockContent blocks={episode.showNotes} />) : ( <p>No show notes available</p>)}Styling not applied
Section titled “Styling not applied”Ensure Tailwind prose plugin is installed:
npm install @tailwindcss/typographyexport default { plugins: [ require('@tailwindcss/typography'), ],};Text formatting not working
Section titled “Text formatting not working”Verify marks are configured in Sanity schema:
marks: { decorators: [ { title: 'Strong', value: 'strong' }, { title: 'Emphasis', value: 'em' }, { title: 'Code', value: 'code' } ]}Content looks unstyled
Section titled “Content looks unstyled”Add prose classes:
<!-- ❌ Missing prose classes --><BlockContent blocks={content} />
<!-- ✅ With prose classes --><BlockContent blocks={content} class="prose max-w-none" />Customization Examples
Section titled “Customization Examples”Example 1: Dark Mode
Section titled “Example 1: Dark Mode”<BlockContent blocks={showNotes} class="prose prose-invert" />Example 2: Custom Color Scheme
Section titled “Example 2: Custom Color Scheme”<BlockContent blocks={showNotes} class="prose prose-blue" />
<style is:global> .prose-blue h2 { color: #3b82f6; }
.prose-blue a { color: #2563eb; }
.prose-blue blockquote { border-left-color: #60a5fa; color: #1e40af; }</style>Example 3: Two-column Layout
Section titled “Example 3: Two-column Layout”<div class="grid grid-cols-1 md:grid-cols-2 gap-8"> <BlockContent blocks={episode.showNotes} class="prose max-w-none" /> <BlockContent blocks={episode.transcript} class="prose max-w-none" /></div>Related Components
Section titled “Related Components”- Episode Page - Use BlockContent for show notes
- About Page - Use BlockContent for page content
Next Steps
Section titled “Next Steps”- Sanity Schemas - Configure block content in Sanity
- Content Management - Add rich content in Sanity Studio
- Customization - Extend BlockContent component